Ebben a tananyagrészben a JavaScript programozási nyelvnek főként azon részéről lesz szó, amivel egy honlapon interakciókat valósíthatunk meg. Ezt gyakran JavaScript DOM-nak szokták nevezni, amiben a DOM a Document Object Model rövidítése.
Amikor a Document Object Modelről beszélünk, gondolhatunk arra, hogy amikor egy böngésző megnyit egy html fájlt, akkor a html elemek betöltődnek a számítógép memóriájába (a betöltődött elemek összességét hívják DOM fának), és JavaScripttel azt tudjuk manipulálni, ami a számítógép memóriájába betöltődött, nem pedig magát a html fájlt. Vagyis ha az adott html oldalt újra betöltjük a böngészővel, akkor a JavaScript által végzett módosítások elvesznek.
Ez elsőre furcsának tűnhet, de például gondoljunk bele, hogy mondjuk egy jelszó esetén rákattintottunk arra az ikonra, hogy látszódjon a jelszó, akkor nem biztos, hogy az oldal következő betöltésekor (ugyanazon a számítógépen) szerencsés, ha látszódik a jelszó. Kivételek persze ebben is vannak, például ha egy honlapon különböző kinézetek közül lehet választani, akkor azt szeretnénk, hogy ha kiválasztunk egy eltérőt az alapbeállítástól, azt ne kelljen a honlap legközelebbi megnyitása után is kiválasztani. Erre több megoldás is van, például a sütik, vagy a bejelentkezés és felhasználói adatok tárolása adatbázisban.
Fájlok tartalmát kliens oldali JavaScripttel (azaz a hagyományos JavaScripttel) nem tudjuk megváltoztatni, biztonsági okokból. Ha esetleg fájlok tartalmát szeretnénk megváltoztatni, ahhoz PHP-t, illetve MySQL-t szoktunk használni, esetleg a PHP helyett szerveroldali JavaScriptet, amit NodeJS-nek szoktak nevezni.
Ebben a tananyagrészben nem lesz szó szerveroldali JavaScriptről (NodeJS-ről), csak kliensoldali JavaScriptről.
Ebben a tananyagrészben nem lesz szó részletesebben a programozással kapcsolatos alapfogalmakról (pl. változó, elágazás, ciklus, tömb, függvény, osztály, objektum), mivel ezekről már írtam egy különálló tananyagot, ahol JavaScript példák is szerepelnek. Ezt a tananyagot azoknak tanácsos a jelen tananyagrész megkezdése előtt elolvasnia, akik teljesen nulláról kezdik most tanulni a JavaScriptet. A tananyag itt érhető el:
Mint ahogy az a tananyag címében látható, a tananyagban szerepelnek C++ példák is, amelyeket nem szükséges elolvasni, megérteni a webfejlesztéshez. Azért lett ez a két nyelv kiválasztva ebben a tananyagban, hogy legyen egy erősen, statikusan típusos és egy gyengén, dinamikusan típusos programozási nyelvre is példa, az előbbi a C++, az utóbbi a JavaScript.
előző tananyagrész: CSS
következő tananyagrész: PHP
Tartalomjegyzék
- hova írhatunk JavaScript kódot
- kommentek (megjegyzések) a kódban
- hogyan nézhetjük meg a JavaScript kód eredményét
- Hello World példák: alert(), console.log(), button + alert(), innerText 1, innerText 2, innerText 3, createElement
- DOM elemek és tulajdonságaik kiíratása console.log() segítségével
- innerHTML
- createElement, appendChild
- dom_element.style.property = "value"; (image slider példa)
- getAttribute(), setAttribute() (jelszó megjelenítése/elrejtése példa)
Milyen feladatokat szoktunk (kliensoldali) JavaScript segítségével megvalósítani egy honlap esetén?
Néhány példa:
- gombra kattintáskor történő művelet elvégzése
- html elemek megjelenítése, elrejtése (pl. legördülő menü vagy slider, azaz az oldal fejlécében lévő váltakozó képek)
- új tartalmak betöltése az adott html oldalra (akár oldalfrissítés nélkül)
- felugró üzenetek (popup) megjelenítése
- html elemek tulajdonságainak megváltoztatása (pl. egy lottózó program esetén a lottószámok kijelölése, vagy a nyerőszámok megjelenítése (az adott szám, vagy a szám hátterének átszínezése))
- üzleti logika (olyan algoritmus, ami nem a megjelenítésért felel, például mondjuk egy aknakeresőben az aknák véletlenszerű elhelyezése)
- űrlapok kitöltésének ellenőrzése (input validation), bár ezt inkább szerver oldalon szokás ellenőrízni (pl. PHP-val, vagy NodeJS-el)
Hova írhatunk JavaScript kódot?
Külön fájlba
Ha egy adott JavaScript kódot több html fájlban is fel szeretnénk használni, akkor külön fájlba érdemes írni a JavaScript kódot. Az átláthatóság, és rendszerezettség kedvéért akkor is érdemes külön fájlba írni a JavaScript kódot, ha azt csak egyetlen html fájlban használjuk fel.
<!-- JS code saved in an other file -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example text in Web browser's window title</title>
<script src="something.js" defer></script>
</head>
<body>
<!-- write your html code here -->
</body>
</html>
Ez esetben a JavaScript kódot a something.js fájlba írjuk, aminek a fenti példakód esetén ugyanabban a könyvtárban kell elhelyezkednie, mint ahol az a fájl található, amibe a fenti html kódot mentettük.
Ezen persze szoktak változtatni is, például ha egy js nevű könyvtárba helyezik el a JavaScript fájlokat. Ekkor például src="js/something.js" vagy src="/js/something.js" attribútumot adhatunk meg a script tageknek.
Fontos, hogyha külön fájlba helyezzük el a JavaScript kódokat, akkor ezek a fájlok ne tartalmazzanak <script></script> nyitó- és záró html tageket.
A script tagek esetén a defer kulcsszó azért felelős, hogy a JavaScript kód akkor legyen betöltve, amikor a html oldal már betöltődött, így nem fordulhat elő az, hogy a JavaScript kód egy olyan html elemre hivatkozik, ami még nem töltődött be.
HTML fájlba a <head></head> html tagek közé <script></script> tageken belül
<!-- JS code in <head></head> -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example text in Web browser's window title</title>
<script>
document.addEventListener("DOMContentLoaded", () => {
/* write your JavaScript code here */
});
</script>
</head>
<body>
<!-- write your html code here -->
</body>
</html>
Ebben az esetben a <script></script> tageket érdemes a </head> záró html tag elé közvetlenül írni.
Ha ezt a módszert használjuk, akkor arról, hogy a JavaScript kód akkor fusson le, amikor már a html fájl betöltődött, a JavaScript kódon belül elhelyezett document.addEventListener("DOMContentLoaded", () => {}); utasítás gondoskodik, másképp fogalmazva a teljes JavaScript kódunkat ennek az utasításnak a kapcsos zárójelei közé helyezzük el.
Külön fájlokba írt JavaScript kód esetén, ha használunk defer kulcsszót, akkor nem szükséges a DOMContentLoaded, de mivel itt a tananyagban nem külön fájlba írjuk a JavaScript kódot, ezért használjuk a DOMContentLoaded eseménykezelőt.
HTML fájlba, közvetlenül a </body> tag elé, <script></script> tageken belül
Sokan szokták ezt a módszert alkalmazni, azért, hogy a JavaScript kód a html kód betöltődése után fusson le. Ebben az esetben nem szükséges "DOMContentLoaded".
<!-- JS code right before </body> -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Example text in Web browser's window title</title>
</head>
<body>
<!-- write your html code here -->
<script>
//write your JavaScript code here
</script>
</body>
</html>
Kommentek a JavaScript kódban
A kommentek (megjegyzések) a programozók feljegyzései, amiket a futtatókörnyezet nem kísérel meg futtathatóvá alakítani.
JavaScriptben ugyanaz a két fajta kommentelés használható, mint a C, C++, Java, C# nyelvekben.
- A // az adott sor végéig tart (nem muszáj a sor elején kezdeni). Nem működik az a "trükk", mint C, C++ nyelvekben, ha a sor végére \ jelet írunk, akkor a // komment a következő sorra is érvényes lesz.
- A /* és */ lehet akár többsoros, vagy akár egy adott soron belüli komment is. Nem muszáj a sor elején kezdeni és nem muszáj a sor végén lezárni. Ilyen kommentek egymásba ágyazása hibát okozhat.
Hogyan nézhetjük meg egy JavaScript kód eredményét
Jelen tananyagrészben html manipulálására használjuk a JavaScript nyelvet, így tehát nem meglepő, ha azt a html fájlt kell megnyitni, amibe a JavaScript kód be lett ágyazva.
Például teszteljük le (másolás, beillesztéssel) az alábbi alcímben szereplő Hello World példák legfelső kódját (vagyis görgessünk le a következő kódrészlethez).
Alapvetően kétféleképp tekinthetjük meg az eredményt.
Az első módszer, ha a kódot bemásoljuk egy txt editorba (pl. notepad.exe vagy pl. Linuxon gedit) vagy egy code editorba (pl. Visual Studio Code vagy notepad++), majd elmentjük html fájlként (pl. valami.html), majd dupla kattintással megnyitjuk a html fájl egy böngészővel.
A második módszer, ha erre a honlapra a html részhez bemásoljuk a kódot:
Ha a JavaScript kód külön fájlban szerepel, akkor a JavaScript fájl tartalmát másoljuk be a JS részhez. <script></script> html tageket ne írjunk bele.
A harmadik módszer, ha egy böngészőben megnyitjuk a fejlesztői eszközöket (developer tools), általában F12 billentyűvel, és ott a konzol (Console) fület megnyitjuk, és oda bemásoljuk a JavaScript kódot. Ámde ez esetben figyeljünk arra, hogy a JavaScript kód a böngészőben megnyitott oldal elemeit fogja tudni elérni és például módosítani. Ez persze egy alert("Hello World"); utasítás esetén jelentéktelen. Ezt a módszert jellemzően rövid JavaScript kódok kipróbálására szokták használni.
A negyedik módszer, ha erre a honlapra <script></script> tagek közé bemásoljuk a JavaScript kódot, vagy pedig bemásoljuk a html-be ágyazott JavaScript kódot (mint például az első Hello World-os példa), de ez utóbbi esetén fontos, hogy a kód nem fog működni, ha nem tartalmaz <script></script> tageket, hanem csak html-t, vagy css-t.
Hello World, avagy az első példakód
Amikor programozási nyelveket tanítanak, általában a Hello World szöveg kiíratása szokott lenni az első példakód, példaprogram.
JavaScript nyelvben a Hello World szöveg kiíratását egész változatos módszerekkel tehetjük meg.
Hello World szöveg kiíratása alert() függvénnyel
<!-- JS Hello World example with alert() function -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with alert() function</title>
<script>
document.addEventListener("DOMContentLoaded", () => {
alert("Hello World");
});
</script>
</head>
<body>
</body>
</html>
Ebben a példában az érdemi JavaScript kódunk mindösszesen egy utasítás: alert("Hello World");
Az alert() függvény a neki átadott szöveget felugró üzenetként kiírja. Ebben a példában akkor, amikor betöltődik a html oldal. Vagyis ha azt szeretnénk, hogy a felirat újra kiíródjon, akkor újra be kell töltenünk a html oldalt (pl. böngészőben F5 billentyűt megnyomva).
Fontos, hogy az alert() függvénynek átadott szöveget tegyük idézőjelek vagy aposztrófok közé, mert ezzel jelezzük a JavaScriptnek, hogy a kód azon részét egy szövegként kell értelmezni (és mondjuk nem egy változó neveként).
Hello World szöveg kiíratása console.log() tagfüggvénnyel
<!-- JS Hello World example with console.log() method -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with console.log() method</title>
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("Hello World");
});
</script>
</head>
<body>
</body>
</html>
Bár a Hello Worldös példák kapcsán ez a módszer nem túl célszerű (inkább hibakeresésre szokták használni), de azért soroltam fel a példák között, mert érdemes arra kitérni, hogy ilyen lehetőség is van.
Ebben a példában az eredmény, vagyis a Hello World szöveg a fejlesztői eszközök (developer tools) konzol (console) fülen jelenik meg. A fejlesztői eszközöket általában F12 billentyűvel nyithajuk meg egy böngészőben.
Hello World szöveg kiíratása gombra kattintással, alert() függvénnyel
<!-- JS Hello World example with button and alert() function -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with button and alert() function</title>
<style>
button { display: block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button#btn_print").addEventListener("click", () => {
alert("Hello World");
});
});
</script>
</head>
<body>
<button id="btn_print">Print Hello World text</button>
</body>
</html>
Ehhez a példához a html részben szükségünk van egy gombra. Általában ha egy adott html elemre szeretnénk hivatkozni (és nem többre), akkor amelyikre hivatkozni akarunk, annak egy id attribútumot adunk meg. Ebben a példában az id attribútum értéke btn_print. Az értékhez az idézőjelek nem tartoznak hozzá.
Ezzel az utasítással választjuk ki ezt a gombot id alapján:
document.querySelector("button#btn_print")
Mivel egy id-ból csak egyetlen lehet egy html fájlban, ezért a button tulajdonképpen felesleges elé, vagyis ez az utasítás teljesen egyenértékű ezzel:
document.querySelector("#btn_print")
Néha mégis azért szokták az id elé írni az adott html elem típusát, mert úgy hibakereséskor átláthatóbb, hogy mit választunk ki.
A querySelector tagfüggvénynek megadott paraméter tulajdonképpen egy CSS szelektor, ezért itt a JavaScript tananyagrészben ezek jelentését nem részletezzük, mivel már a CSS tananyagrészben átvettük a legtöbbet.
Jelen példánál nem annyira magától értetődő arról beszélni, hogy mi a következő utasítás, hiszen ez az utasítás valójában egyetlen utasítás, csak egy összetett utasítás:
document.querySelector("button#btn_print").addEventListener("click", () => { /*statements*/ });
Ezt gyakran két utasításra szokták bontani.
const btnprint_dom = document.querySelector("button#btn_print");
btnprint_dom.addEventListener("click", () => { /*statements*/ });
Itt az első utasításban a document.querySelector("button#btn_print") kifejezés által visszaadott értéket lementjük egy változóba, majd a második utasításban a változóba elmentett értékre hivatkozunk, amihez nem kell újra leírni a document.querySelector("button#btn_print") kifejezést, hanem elég csak leírni a változó nevét.
Tekintsük most ezt az utasítást:
btnprint_dom.addEventListener("click", () => { /*statements*/ });
Az .addEventListener("click", () => { /*statements*/ }); kifejezéssel egy úgynevezett click eseményt rendelünk jelen esetben a btnprint_dom nevű változóban eltárolt gombhoz, és ez azt fogja eredményezni, hogyha erre a gombra rákattintunk, akkor ebben a kifejezésben .addEventListener("click", () => { /*statements*/ }); azok az utasítások, amiket a kapcsos zárójelek közé írunk, lefutnak majd akárhányszor csak a gombra kattintunk.
Tehát például ez az utasítás:
document.querySelector("button#btn_print").addEventListener("click", () => { alert("Hello World"); });
Azt eredményezi, hogyha erre a gombra kattintunk:
<button id="btn_print">Print Hello World text</button>
Akkor ez az utasítást fog lefutni:
alert("Hello World");
Hello World szöveg beillesztése már létező html elem tartalmaként, gombra kattintással
<!-- JS Hello World example with button and innerText -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with button and innerText</title>
<style>
button { display: block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button#btn_print").addEventListener("click", () => {
document.querySelector("p#hello_world_container").innerText = "Hello World!";
});
});
</script>
</head>
<body>
<button id="btn_print">Print Hello World text</button>
<h1>"Hello World!" text inserted here by Javascript:</h1>
<p id="hello_world_container"></p>
</body>
</html>
Ebben a példában már a html-be (valójában a DOM-ba) írjuk bele a "Hello World" szöveget. Ehhez a html részben már előre létrehoztunk egy üres bekezdést (<p></p> tageket), illetve a bekezdésnek adtunk egy id-t, ami alapján a JavaScriptben hivatkozhatunk rá.
Ezzel az utasítással választhatjuk ki a bekezdést:
document.querySelector("p#hello_world_container")
A document.querySelector() tagfüggvény által visszaadott objektum innerText adattagját kell módosítanunk ahhoz, hogy a bekezdés tartalmát módosítsuk.
Ha ezt a próbát kipróbáljuk, csak a gomb első megnyomását követően vehetünk észre változást. Persze valójában, amikor másodjára, harmadjára rákattintunk a gombra, a hello_world_container id-vel ellátott bekezdés (<p></p> html tagek) tartalma törlődik, és kicserélődik szintén egy Hello World! szövegre, de ez olyan gyorsan történik meg, hogy nem vehetünk észre semmilyen változást.
Ha egy kicsit továbbfejlesztjük a példát, létrehozhatunk egy törlés gombot, ami törli a bekezdésben lévő szöveget (akkor is törölné, ha nem Hello World! lenne a szöveg), és ha a törlés után újra a Print gombra kattintunk, akkor pedig beillesztődik a Hello World! szöveg:
<!-- JS Hello World example with button and innerText 2.0 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with button and innerText 2.0</title>
<style>
button { display: inline-block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const p_hello_world_dom = document.querySelector("p#hello_world_container");
document.querySelector("button#btn_print").addEventListener("click", () => {
p_hello_world_dom.innerText = "Hello World!";
});
document.querySelector("button#btn_delete").addEventListener("click", () => {
p_hello_world_dom.innerText = "";
});
});
</script>
</head>
<body>
<button id="btn_print">Print Hello World text</button>
<button id="btn_delete">Delete text</button>
<h1>"Hello World!" text inserted here by Javascript:</h1>
<p id="hello_world_container"></p>
</body>
</html>
A hello_world_container id-vel ellátott bekezdés tartalmának a törtésére nincs külön utasítás, hanem egyszerűen csak beállítunk szövegnek egy üres stringet, azaz az idézőjelek közé nem írunk semmit: innerText = ""
Esetleg még tovább bonyolíthatjuk ezt az egyszerű példakódot, ha csak akkor lehet rákattintani egy gombra, ha tudjuk, hogy az a gomb csinál is valamilyen érdemleges módosítást (vagyis ha nincs szöveg, akkor ne működjön a törlés gomb, ha újra van szöveg, akkor újra működhet a törlés gomb).
<!-- JS Hello World example with button and innerText 3.0 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with button and innerText 3.0</title>
<style>
button { display: inline-block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const p_hello_world_dom = document.querySelector("p#hello_world_container");
const btn_print_dom = document.querySelector("button#btn_print");
const btn_delete_dom = document.querySelector("button#btn_delete");
btn_print_dom.addEventListener("click", () => {
p_hello_world_dom.innerText = "Hello World!";
btn_delete_dom.disabled = false;
btn_print_dom.disabled = true;
});
btn_delete_dom.addEventListener("click", () => {
p_hello_world_dom.innerText = "";
btn_delete_dom.disabled = true;
btn_print_dom.disabled = false;
});
});
</script>
</head>
<body>
<button id="btn_print">Print Hello World text</button>
<button id="btn_delete" disabled>Delete text</button>
<h1>"Hello World!" text inserted here by Javascript:</h1>
<p id="hello_world_container"></p>
</body>
</html>
Ahhoz, hogy egy gomb az oldal betöltődésekor még ne működjön, azt html-ben kell beállítani úgy, hogy az adott gomb html tagjének az attribútumai közé beírjuk a disabled kulcsszót, például így:
<button id="btn_delete" disabled>Delete text</button>
Ha JavaScripttel egy gombra történő kattintást követően szeretnénk letiltani egy másik gombot, akkor azt például így tehetjük meg:
document.querySelector("button#btn_delete").disabled = true;
A fenti példában ennek megfeleő utasítás:
btn_delete_dom.disabled = true;
Ha pedig újra működővé szeretnénk tenni a gombot, akkor ilyesmi utasítást használhatunk:
document.querySelector("button#btn_delete").disabled = false;
Fontos, hogy a true vagy false értékek logikai literálok, nem pedig szövegek (string literálok), ezért ne tegyük őket idézőjelek közé.
Ha nem szeretnénk fejben nyomon követni, hogy egy gomb mikor van letiltva és mikor használható, hanem csak azt szeretnénk, hogy a letiltott gombból nem letiltott gomb legyen, illetve a nem letiltottból letiltott, akkor használhatjuk ezt a kifejezést:
btn_delete_dom.disabled = !btn_delete_dom.disabled;
Hello World szöveg beillesztése új html elemként, gombra kattintással
<!-- JS Hello World example with button, createElement, appendChild -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JS Hello World example with button, createElement, appendChild</title>
<style>
button { display: inline-block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button#btn_print").addEventListener("click", () => {
const new_p = document.createElement("p");
new_p.innerText = "Hello World";
document.body.appendChild(new_p);
});
document.querySelector("button#btn_delete").addEventListener("click", () => {
document.querySelectorAll("p").forEach(element => element.remove());
});
});
</script>
</head>
<body>
<button id="btn_print">Print Hello World text</button>
<button id="btn_delete">Delete texts</button>
<h1>"Hello World!" text inserted here by Javascript:</h1>
</body>
</html>
A document.createElement() tagfüggvénnyel tudunk új html elemeket létrehozni, amit egy változóba szoktunk lementeni. A createElement tagfüggvénynek paraméterként a létrehozni kívánt html elem típusát kell megadnunk, pl. egy bekezdés esetén p.
const new_p = document.createElement("p");
A bekezdésbe (<p></p>-be) illesztendő szöveget itt is az innerText adattagnak adjuk értékül.
new_p.innerText = "Hello World";
Egy HTMLElement appendChild() tagfüggvénnyel adhatjuk hozzá a DOM fához az újonnan létrehozott html elemet, jelen példában egy <p></p>-t.
document.body.appendChild(new_p);
Ha esetleg nem a body-hoz szeretnénk hozzáadni, hanem mondjuk egy div-hez, akkor ilyesmi utasítást használhatunk:
document.querySelector("div#id_of_div").appendChild(new_p);
Ebben a példában ha többször kattintunk a gombra, akkor több Hello World feliratot is beilleszthetünk.
Mellékesen felhívjuk a figyelmet arra, hogy ebben a példában a törlés gomb minden bekezdést töröl. Bár ebben példában csak Hello World szöveget tartalmazó bekezdések vannak, ha esetleg lenne más is, ezzel a módszerrel az is törlődne.
Ezt például úgy oldhatnánk meg, hogy a létrehozott elemhez egy CSS osztályt is hozzárendelünk, aminek a neve legyen mondjuk hello_class:
const new_p = document.createElement("p");
new_p.classList.add("hello_class");
new_p.innerText = "Hello World";
document.body.appendChild(new_p);
A törlésnél pedig csak az ilyen osztállyal rendelkező bekezdéseket töröljük:
document.querySelectorAll("p.hello_class").forEach(element => element.remove());
DOM elemek és tulajdonságaik kiíratása console.log() segítségével
A console.log() tagfüggvényt általában debuggoláshoz használjuk. Például változók értékeit íratjuk ki mondjuk egy általunk készített függvényben, és ez által próbáljuk megtalálni, hogy valamilyen műveletben hol lehet a hiba. Viszont a console.log() segítségével nem csak változók értékét írathatjuk ki, hanem például a html tageknek megfelelő HTMLElement objektumok minden egyes tulajdonságait.
const something_dom = document.querySelector("#id_of_someting");
console.log(someting_dom);
innerHTML
Egy html fájlban lévő html tageknek megfelelő JavaScript objektumok (a DOM objektumok javarésze, a HTMLElement típusú objektumok) rendelkeznek innerHTML adattaggal, amivel egy html tagen belül lévő html tageket kérdezhetünk le vagy módosíthatunk.
innerHTML adattag értékének kiíratása
Ebben az egyszerű példában egy <ul></ul> lista innerHTML-jét íratjuk ki alert() függvénnyel, gombra kattintásra. Ezt például hibakereséshez használhatjuk.
<!-- alert(innerHTML) -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>alert(innerHTML)</title>
<style>
button { display: inline-block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button#button_example").addEventListener("click", () => {
const ul_dom = document.querySelector("ul#ul_example");
alert(ul_dom.innerHTML);
});
});
</script>
</head>
<body>
<h1>List example</h1>
<ul id="ul_example">
<li style="color:red;">List element 1</li>
<li>List element 2</li>
<li>List element 3</li>
</ul>
<button id="button_example">Print innerHTML</button>
</body>
</html>
innerHTML adattag módosítása
Ebben a példában egy listához adunk hozzá gombra kattintással 3 új listaelemet (<li></li>).
A listaelemek szövegét egy tömbből vesszük, és azokhoz hozzáfűzzük azt is, hogy hanyadik elemről van szó. Ezt úgy oldjuk meg, hogy JavaScriptben minden tömbnek van egy forEach tagfüggvénye, amivel végighaladhatunk a tömb elemein. Ennek a tagfüggvénynek két paramétert adunk meg, az egyik, hogy az aktuális elemre milyen névvel hivatkozhatunk (a példában element), a másik, hogy az aktuális elem indexére milyen névvel hivatkozhatunk.
Egy DOM elem innerHTML-jét például így módosíthatjuk:
var_name.innerHTML = "something";
Jelen példa esetén:
list_dom.innerHTML = "<li>something</li>";
Ez a két utasítás törli az adott DOM elem eddigi innerHTML-jét, és az új értékkel írja felül.
Ha azt szeretnénk, hogy az adott DOM elem eddigi innerHTML-je ne törlődjön, hanem ahhoz szeretnénk új tartalmat hozzáfűzni, akkor azt például így érhetjük el:
list_dom.innerHTML = list_dom.innerHTML + "<li>something</li>";
Ennek a rövidítése:
list_dom.innerHTML += "<li>something</li>";
Ha azt szeretnénk, hogy a tömb elemek értékét felhasználjuk az innerHTML-hez hozzáfűzött szövegben, azt például így érhetjük el:
list_dom.innerHTML += "<li>" + element + "</li>";
A modern JavaScriptben már használhatunk úgynevezett template literálokat, amik abban különböznek a stringektől, hogy egy speciális jelöléssel a stringen belül változók, kifejezések értékét használhatjuk fel. Ez az utasítás ugyanazt eredményezi, mint a legutóbbi:
list_dom.innerHTML += `<li>${element}</li>`;
Ebben az utasításban a ${} kifejezésben a kapcsos zárójelek közötti kifejezés kiértékelődik, majd stringként behelyettesítődik a teljes stringbe. Figyeljünk arra, hogy ilyen template literálok csak akkor működnek, ha egy stringet `` (backtick) karakterek közé írunk.
Ha az indexet is szeretnénk hozzáfűzni, azt például így érhetjük el:
list_dom.innerHTML += `<li>${element} ${element_index}</li>`;
Mivel a legtöbb programozási nyelvben a tömbök 0-tól vannak indexelve (az első elem indexe 0, az utolsó elem indexe elemszám-1) ezért ahhoz, hogy az első listaelemben listaelem0 helyett listaelem1 legyen kiírva, az indexhez a template literálban hozzá kell adnunk 1-et:
list_dom.innerHTML += `<li>${element} ${element_index + 1}</li>`;
Ebben a példában az is meg van oldva, hogyha többször rákattintunk az "Insert innerHTML" feliratú gombra, akkor ne mindig 1-től 3-ig tartson az újabb listaelemek száma, hanem például az első beillesztett 3 elem után a következő listaelemek száma 4, 5, 6 legyen.
Mivel JavaScriptben nincs függvényen belül statikus változó, ezért ehhez egy globális változót használunk, amit a click eseményen kívül hozunk létre 0 kezdőértékkel.
A forEach ciklus lefutása után ehhez a számhoz mindig hozzáadjuk a tömb elemszámát a példában list_js.length.
Ezt a növelgetett számot a cikluson belül mindig hozzáadjuk az adott elem indexéhez:
list_dom.innerHTML += `<li>${element} ${numberof_elems_so_far + element_index + 1}</li>`;
Ha töröljük a lista innerHTML-jét a button_delete id-val ellátott gombbal, akkor érdemes ezt a számot lenullázni, vagyis 0-t adni neki értékül.
numberof_elems_so_far = 0;
<!-- innerHTML = -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>innerHTML</title>
<style>
button { display: inline-block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
let numberof_elems_so_far = 0;
document.querySelector("button#button_insert").addEventListener("click", () => {
const list_js = [ "li text example", "li text example", "li text example"];
const list_dom = document.querySelector("ul#ul_example");
/*list_dom.innerHTML = "";*/
list_js.forEach((element, element_index) => {
list_dom.innerHTML += `<li>${element} ${numberof_elems_so_far + element_index + 1}</li>`;
});
numberof_elems_so_far += list_js.length;
});
document.querySelector("button#button_delete").addEventListener("click", () => {
const list_dom = document.querySelector("ul#ul_example");
list_dom.innerHTML = "";
numberof_elems_so_far = 0;
});
});
</script>
</head>
<body>
<h1>List example</h1>
<ul id="ul_example">
</ul>
<button id="button_insert">Insert innerHTML</button>
<button id="button_delete">Delete innerHTML</button>
</body>
</html>
Tekintsünk egy másik innerHTML-es példát. Ebben a példában random generált számokat adunk értékül a rendezetlen listának (<ul></ul>), ha egy szám pozitív, piros színnel írjuk ki, ha negatív, akkor pedig kékkel.
A random_number matematikai hátterébe nem megyünk bele ebben a tananyagban, az, hogy milyen értékkel szorozzuk a Math.random() tagfüggvény által visszaadott értéket, illetve milyen értéket adjunk hozzá, a W3Schools tananyagában alaposan le van írva:
Ebben a példában a random_number függvény egész számokat ad vissza, beleértve a minimum és a maximum értéket.
Az innerHTML-nek (nem üres) értéket adó gombra kattintáskor először töröljük a lista eddigi innerHTML-jét:
list_dom.innerHTML = "";
Az elemszámot is random generáljuk egy 2 és 10 közötti értékkel:
const number_of_items = random_number(2,10);
A for ciklusban most nem használjuk fel a ciklusváltozó értékét (i), hanem azt csak arra használjuk, hogy a ciklus elemszámszor fusson le.
A -100 és 100 közötti random generált számot alapvetően így tudjuk hozzáfűzni a rendezetlen lista innerHTML-jéhez:
list_dom.innerHTML += `<li>${actual_item}</li>`;
A template literálok esetén jó eséllyel sokszor fogunk találkozni ternáris operátorral, ami valójában egy if-else elágazás inlineosítása. Például ennek a kifejezésnek...
actual_item<0 ? "class='negative_num'" : "class='positive_num'"
...ha az actual_item értéke kisebb 0-nál, akkor az eredménye ez a string literál: class='negative_num'
...ha pedig az actual_item értéke nagyobb, vagy egyenlő mint 0, akkor ez a string literál: class='positive_num'
A string literálokat itt most idézőjelek közé tesszük: "class='negative_num'" ahol az utolsó két karakter egy aposztróf és egy idézőjel egymás mellett. Jelen esetben akár szóközt is tehetnénk közéjük a jobb láthatóság kedvéért, de ügyeljünk arra, hogy akkor az a szóköz a string literál része lesz, ami jelen példa esetén mindegy.
JavaScriptben az aposztróf és az idézőjel egyaránt használható string literálok elejének és végének a jelölésére. Például írhattuk volna ezt is: 'class="negative_num"', ahol az utolsó két karakter egy idézőjel és egy aposztróf.
Így tehát ebben az utasításban...
list_dom.innerHTML += `<li ${actual_item<0 ? "class='negative_num'" : "class='positive_num'"}>${actual_item}</li>`;
Ha az actual_item értéke negatív, akkor a nyitó <li> tag attribútumaként class='negative_num' lesz megadva, ha pedig az actual_item értéke 0 vagy pozitív, akkor class='positive_num'.
<!-- innerHTML = random numbers -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>innerHTML = random numbers</title>
<style>
button { display: inline-block; }
.positive_num { color: red; }
.negative_num { color: blue; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const random_number = (min, max) => {
return (Math.floor(Math.random() * (max - min + 1) ) + min);
};
document.querySelector("button#button_insert").addEventListener("click", () => {
const list_dom = document.querySelector("ul#ul_example");
list_dom.innerHTML = "";
const number_of_items = random_number(2,10);
for (let i = 0; i < number_of_items; ++i) {
let actual_item = random_number(-100, 100);
list_dom.innerHTML += `<li ${actual_item<0 ? "class='negative_num'" : "class='positive_num'"}>${actual_item}</li>`;
}
});
document.querySelector("button#button_delete").addEventListener("click", () => {
const list_dom = document.querySelector("ul#ul_example");
list_dom.innerHTML = "";
});
});
</script>
</head>
<body>
<h1>Random numbers</h1>
<ul id="ul_example">
</ul>
<button id="button_insert">Replace innerHTML</button>
<button id="button_delete">Delete innerHTML</button>
</body>
</html>
Esetleg még annyival meg lehetne bonyolítani ezt a példát, hogy ha pont 0-ra jön ki a random generált érték, akkor ne színezzük be, de mivel egy switch-case elágazás nem írható le inline kifejezéssel, ezért ezt a feladatot csak egymásba ágyatott ? : operátorokkal tudjuk megoldani, ami nem túl jól átlátható:
list_dom.innerHTML += `<li ${actual_item<0 ? "class='negative_num'" : actual_item>0 ? "class='positive_num'" : ""}>${actual_item}</li>`;
Ehelyett szoktak a Java (nem JavaScript) nyelvben megszokott StringBuilderhez hasonló megoldást alkalmazni:
let list_dom_innerhtml_string = "<li ";
if (actual_item<0) { list_dom_innerhtml_string += "class='negative_num'"; }
else if (actual_item>0) { list_dom_innerhtml_string += "class='positive_num'"; }
/*else { list_dom_innerhtml_string += "" }*/
list_dom_innerhtml_string += `>${actual_item}</li>`;
list_dom.innerHTML += list_dom_innerhtml_string;
...ami viszont nem írható egy sorba.
Fontos: innerHTML-hez ne adjunk hozzá nyitó tageket önmagában, mert akkor automatikusan hozzáadódik rögtön utána egy zárótag. Ez például hibás html kódot fog eredményezni:
something.innerHTML += "<ul>";
something.innerHTML += "<li>something</li>";
something.innerHTML += "</ul>";
Habár logikusan azt gondolnánk, hogy ezek az utasítások ezt az innerHTML-t eredményezik:
<ul><li>something</li></ul>
Valójában ez az utasítássorozat ezt fogja eredményezni:
<ul></ul><li>something</li></ul>
Mégpedig azért, mert amikor ez az utasítás végrehajtásra kerül...
something.innerHTML += "<ul>";
...akkor a nyitó <ul> taghez automatikusan hozzáadódik egy záró </ul> tag is.
A megoldás: vagy fűzzük hozzá részenként az innerHTML-nek értékül adandó szöveget egy stringbe, majd amikor befejeztük, akkor ezt a stringet adjuk értékül az innerHTML-nek:
something_innerhtml_string = "";
something_innerhtml_string += "<ul>";
something_innerhtml_string += "<li>something</li>";
something_innerhtml_string += "</ul>";
something.innerHTML = something_innerhtml_string;
Vagy pedig használjunk document.createElement, illetve document.appendChild tagfüggvényeket innerHTML helyett.
createElement(), appendChild()
Bár innerHTML-el, és template literálokkal a legtöbb esetben meg tudjuk oldani, ha új tartalmat szeretnénk hozzáfűzni egy böngészőben megnyitott oldalhoz, illetve ha egy már meglévő tartalmat szeretnénk kicserélni, frissíteni szeretnénk, viszont ha az egymásba ágyazott html tagek viszonylag bonyolultabb struktúrát alkotnak, akkor érdemesebb document.createElement és document.appendChild tagfüggvényeket használni, mint innerHTML-t módosítani. Például táblázatokat érdemesebb így felépíteni.
Ebben a példában gombra kattintással létrehozunk egy táblázatot (document.createElement), ami random generált számokat tartalmaz (a random generált számokat a táblázat készítése közben hozzuk létre), majd ezt a táblázatot hozzáfűzzük a DOM fához document.appendChild tagfüggvénnyel ahhoz, hogy megjelenjen a html oldalon. Amikor új táblázatot készítünk, ha volt már előző táblázat, azt töröljük, és az új táblázatra cseréljük ki.
Tekintsük a JavaScript kódot.
A random szám generáló függvény már ismert lehet az előző példából.
create_table függvény
A táblázatot a create_table függvényben készítjük el, aminek két paraméter adható, a táblázat sorainak és oszlopainak száma: num_of_rows és num_of_cols változónevek. Amikor egyébként ezt a függvényt meghívjuk, 2 és 10 közötti random generált számokat adunk neki paraméterül, ami azt jelenti, hogy a sorok és az oszlopok száma legalább 2 és legfeljebb 10 lehet.
A táblázat html tagjeit felülről-alulra haladva építjük fel. Legelsőnek létrehozzuk a <table></table> tageket:
const table_dom = document.createElement("table");
Aztán a <thead></thead> tageket:
const thead_dom = document.createElement("thead");
Ezután létrehozzuk a theadbe elhelyezendő tageket, majd ha ha feltöltöttük őket tartalommal, hozzáfűzzük őket a theadhez.
Ne felejtsük el hogy a <thead></thead> tageken belül lennie kell egy táblázat sorát jelző tagnek is (<tr></tr>), és a tr-en belül helyezhetjük el a táblázat fejléc cellákat <th></th>.
const thead_row_dom = document.createElement("tr");
Mivel az oszlopokat és a sorokat számozni fogjuk, ezért a bal felső sarokban lévő cellában üres szöveg lesz. Az üres szöveget táblázatokban (ami a non-breaking space rövidítése) karakterrel szokták jelezni.
Először tehát a theadben lévő tr-hez létrehozunk és hozzáadunk egy üres szöveget tartalmazó táblázat fejléc cellát (<th></th>). A létrehozást követően, még a thead-hez való hozzáfűzés előtt az innerHTML adattagjának értékül adjuk az " " string literált.
const th_top_left_cell_dom = document.createElement("th");
th_top_left_cell_dom.innerHTML = " ";
thead_row_dom.appendChild(th_top_left_cell_dom);
Ügyeljünk arra, hogy az karakter-t ne az innerText adattagnak adjuk értékül, hanem az innerHTML-nek, különben nem lesz speciális html karakterként értelmezve, hanem maga az szöveg íródik bele a cellába.
Ezt követően for ciklussal elkészítjük a táblázat fejlécében lévő oszlopcímeket (th DOM elemeket Col1, Col2, satöbbi szöveggel).
for (let i = 1; i <= num_of_cols; ++i) {
const th_cell_dom = document.createElement("th");
th_cell_dom.innerText = `Col${i}`;
thead_row_dom.appendChild(th_cell_dom);
}
A for ciklus itt nyugodtan mehet 1-től elemszámig. Mivel nem tömböket indexelünk, így nem okoz problémát, ha a for ciklusban a ciklusváltozó (i) értéke az első lépésben nem 0, hanem 1, utolsó lépésben pedig nem elemszám-1 hanem elemszám. Bár a különbség csak annyi lenne, hogy 0 esetén a template literálban Col${i+1} kifejezés szerepelne:
th_cell_dom.innerText = `Col${i}`;
...1 esetén pedig Col${i}:
th_cell_dom.innerText = `Col${i}`;
A Col az angol column (oszlop) szó rövidítése.
A táblázat fejlécével <thead></thead> készen vagyunk. Először a <tr></tr>-t fűzzük hozzá a <thead></thead>-hez, azt követően a <thead></thead>-et a <table></table>-hez:
thead_dom.appendChild(thead_row_dom);
table_dom.appendChild(thead_dom);
Következhet tehát a <tbody></tbody> tartalmának elkészítése.
Először létherozzuk a tbody DOM elemet, amire a példában tbody_dom névvel hivatkozhatunk ezen utasítás végrehajtását követően:
const tbody_dom = document.createElement("tbody");
Ha az adatok táblázatban vannak, és a táblázatnak nem csak egy sorát, hanem több sorát is be kell járnunk, akkor arra két egymásbaágyazott ciklust szoktunk használni. Az egymásbaágyazott ciklusok megértésénél segít, ha arra gondolunk, hogy amíg a külső ciklus csak egyszer fut le, a belső ciklus teljesen lefut, és ahányszor a külső ciklus lefut (vagyis a blokkjában lévő utasításk egyszer végrehajtódnak), annyiszor fut le a belső ciklus teljesen.
Jelen példában tekintsük a külső ciklus egyetlen lefutását. Először léthehozunk egy sort:
const tbody_row_dom = document.createElement("tr");
Aztán ehhez a sorhoz hozzáadunk egyetlen cellát még a külső ciklusban, amibe a Row1, Row2, satöbbi szöveget írjuk:
const td_cell_dom = document.createElement("td");
td_cell_dom.innerText = `Row${i}`;
tbody_row_dom.appendChild(td_cell_dom);
Mint ahogy a thead-nél, itt is érdemes a külső ciklust 1-től a sorok számáig futtatni...
or (let i = 1; i <= num_of_rows; ++i) {
...mivel ebben az utasításban...
td_cell_dom.innerText = `Row${i}`;
...i helyett nem kell i+1-et írni (ha a ciklus 0-tól elemszám-1-ig menne, akkor Row${i} kifejezés helyett Row${i+1} kifejezést kellene használni.
Fontos, hogy azért, mert a sorszámokat tartalmazó cellákat minden sor elejéhez hozzáfűzzük, nem lesz eggyel kevesebb oszlopunk, épp ellenkezőleg, ezzel együtt eggyel több oszlopa lesz a táblázatnak.
A belső ciklus minden egyes teljes lefutásakor egy sort (<tr></tr>) töltünk fel random generált számokkal, amiket <td><td> hagyományos (nem fejléc) cellákba helyezünk el.
const td_cell_dom = document.createElement("td");
td_cell_dom.innerText = rand_num(-100,100);
tbody_row_dom.appendChild(td_cell_dom);
A belső ciklus ebben a példában 0-tól halad elemszám-1-ig, de ez lényegtelen, mivel a ciklusváltozó értékét nem használjuk fel a ciklusban. Haladhatna akár 1-től elemszámig, ez nem változtatna semmit a példán.
for (let j = 0; j < num_of_cols; ++j) {
Ne feledjük, hogy a külső ciklus egy adott lefutása azt jelenti, hogy készen vagyunk a táblázat egy újabb sorával, viszont ezt a sort hozzá kell fűznünk a tbodyhoz:
tbody_dom.appendChild(tbody_row_dom);
Ha a külső ciklus teljesen lefutott, az azt jelenti, hogy kész vagyunk a táblázat minden sorával. Ekkor a tbody-t hozzáfűzzük a táblázathoz (<table></table>):
table_dom.appendChild(tbody_dom);
Ami azt jelenti, hogy kész a táblázat, így tehát a táblázatot is hozzáfűzhetjük a DOM fához, hogy megjelenjen a honlapon:
document.body.appendChild(table_dom);
remove_table() függvény
Bár a táblázat törléséhez használható remove_table() függvény ránézésre nem túl bonyolult, sok kezdő számára nem biztos, hogy magától értetődő.
A lényeg, hogy megvizsgáljuk benne, hogy van-e már előzőleg generált táblázat.
Tekintsük először azt az esetet, hogy nincs még táblázat. Ekkor az első utasítás null értéket ad vissza:
let table_dom = document.querySelector("table");
Tehát a table_dom értéke null lesz.
Az if (table_dom) { kifejezés esetén a null érték hamissá konvertálódik, tehát az if blokkjában lévő utasítás nem fut le.
Ha van már előzőleg generált táblázat, a document.querySelector("table") kifejezés egy HTMLTableElement típusú értéket ad vissza, ami az if (table_dom) { kifejezésben igazzá konvertálódik, ezáltal az if blokkjában lévő utasítás végrehajtásra kerül.
Az insert gombra kattintáskor először futtatjuk a remove_table() függvényt, majd a create_table() függvényt, mivel az új táblázatot a régi helyére szeretnénk generálni. A create_table() függvénynek átadunk két random generált szám paramétert, a sorok és az oszlopok számát. Egyébként a kódot akár úgy is átírhatnánk, hogy konkrét értékeket adnánk meg a sorok és az oszlopok számának.
A delete gombra való kattintáskor pedig csak meghívjuk a remove_table() függvényt
<!-- createElement, table, random numbers -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>createElement, table, random numbers</title>
<style>
button { display: inline-block; }
table {
width: 100%;
text-align: center;
padding: 0.3em;
}
thead tr { color: white; background-color: #1e3a8a; }
tbody tr td:first-of-type {
font-weight: bold;
color:white;
background-color: #1e3a8a;
}
tr {
padding: 0.3em;
}
table tr:nth-of-type(even) {
background-color: #e0f2fe;
}
table tr:hover, tr:hover td:first-of-type {
color: black;
background-color: #bfdbfe;
}
th, td {
padding: 0.3em;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const rand_num = (min, max) => {
return (Math.floor(Math.random() * (max - min + 1) ) + min);
};
const create_table = (num_of_rows, num_of_cols) => {
const table_dom = document.createElement("table");
//thead
const thead_dom = document.createElement("thead");
const thead_row_dom = document.createElement("tr");
//top left corner in the table with no text
const th_top_left_cell_dom = document.createElement("th");
th_top_left_cell_dom.innerHTML = " ";
thead_row_dom.appendChild(th_top_left_cell_dom);
//cells in thead with text Col1, Col2, ...
for (let i = 1; i <= num_of_cols; ++i) {
const th_cell_dom = document.createElement("th");
th_cell_dom.innerText = `Col${i}`;
thead_row_dom.appendChild(th_cell_dom);
}
thead_dom.appendChild(thead_row_dom);
table_dom.appendChild(thead_dom);
//tbody
const tbody_dom = document.createElement("tbody");
for (let i = 1; i <= num_of_rows; ++i) {
const tbody_row_dom = document.createElement("tr");
//in every row, first cell is with text Row1, Row2, ...
const td_cell_dom = document.createElement("td");
td_cell_dom.innerText = `Row${i}`;
tbody_row_dom.appendChild(td_cell_dom);
//remaining cells with random generated numbers
for (let j = 0; j < num_of_cols; ++j) {
const td_cell_dom = document.createElement("td");
td_cell_dom.innerText = rand_num(-100,100);
tbody_row_dom.appendChild(td_cell_dom);
}
tbody_dom.appendChild(tbody_row_dom);
}
table_dom.appendChild(tbody_dom);
document.body.appendChild(table_dom);
}
const remove_table = () => {
let table_dom = document.querySelector("table");
if (table_dom) {
table_dom.remove();
}
}
document.querySelector("button#button_insert").addEventListener("click", () => {
remove_table();
const rows = rand_num(2,10);
const cols = rand_num(2,10);
create_table(rows,cols);
});
document.querySelector("button#button_delete").addEventListener("click", () => {
remove_table();
});
});
</script>
</head>
<body>
<h1>Random numbers</h1>
<button id="button_insert">Insert table</button>
<button id="button_delete">Delete table</button>
</body>
</html>
A példakód megtekinthető itt is:
Kapcsolódó referencia tananyag:
dom_element.style.property = "value";
Egy DOM elem stílusának módosítására az egyik módszer, ha ehhez hasonló utasítást használunk:
dom_element.style.property = "value";
Konkrét példa:
text_example_1_dom.style.backgroundColor = "green";
Ennek például az az egyik hátránya, hogy amit módosítunk, az csak körülményesen vonható vissza. Ha visszavonni szeretnénk, akkor arra inkább az element.classList.add("classname"); vagy az element.classList.remove("classname"); utasításokat szoktuk használni.
Image slider példa
Egy sliderben jellemzően a honlap tetején váltakozó képek jelennek meg, amikor az egyik kép eltűnik, megjelenik a következő kép.
Ez a példakód konkrétan a metros.hu honlapomon használt slider, azzal a különbséggel, hogy a hozzá használt képek ide a blog.hu-ra lettek feltöltve, tehát tárhelyileg függetlenek a metros.hu-tól, esetleg ha ott frissülne a slider (például más képeket használnék fel hozzá, akkor nem kellene ezt a példakódot átírni ahhoz, hogy ne működjön rosszul).
Ha valaki esetleg megtekinti a metros.hu honlapot, hogy ott hogy működik ez a slider, arra azért felhívnám a figyelmet, hogy mobil nézetben nem jelenik meg, csak desktop nézetben.
Bár a kód elég alaposan el van látva angol kommentekkel, azért ebben a tananyagrészben magyarul is áttekintjük, hogy melyik utasítás mit csinál.
Induljunk ki abból, hogy a példakód <body> és </body> részén belül van egy kép. Itt ebben a blog.hu-s példakódban, ami csak a slidert tartalmazza, semmi egyebet a honlap fejlécéből, a kép html kódja így néz ki:
<img src="https://m.blog.hu/it/itkezdoknek/image/metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg" alt="Széll Kálmán tér, fortepan 222706" title="Széll Kálmán tér, fortepan 222706">
Mint ahogy azt már említettem, a metros.hu-n ebben csak a kép elérési útját tekintve van különbség:
<img src="/images/header/metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg" alt="Széll Kálmán tér, fortepan 222706" title="Széll Kálmán tér, fortepan 222706">
Ez a kép akkor töltődik be, amikor a böngészővel először futtatjuk le a kódot, vagy amikor frissítjük a böngészőt (pl. F5 gombbal).
Ez a kép benne van egy divben, aminek az id-ja "image_slider", ez alapján hivatkozunk erre a képre, amikor ki akarjuk cserélni egy másik képre.
<div id="image_slider"></div>
Tekintsük most a JavaScript kódot.
Az elején egy tömbben fel vannak sorolva a soron következő képek (és a legelső kép is), a hozzájuk tartozó alt attribútumokkal. (A legelső képet egyébként azért kell itt felsorolni, mert ha később lecseréljük egy másik képre, akkor amikor pont megint ez a kép lesz a soronkövetkező, amire ki akarjuk cserélni az addigi képet, akkor szükség lesz ezekre az adatokra).
Minden egyes tömbelem egy objektum (az objektumorientált programozás legfőbb fogalmára gondolunk itt, mint objektum), aminek adattagként vannak megadva a fáljnevek, illetve az alt tulajdonságok.
const slider_images_array = [{/*...*/},{/*...*/},{/*...*/},{/*...*/}];
Ez a tömb a függvényen kívül lett létrehozva, azért, hogy ne kelljen a függvény minden egyes futtatásakor újra meg újra létrehozni.
Tekintsük most a függvénynek azt a részét, ami azért felel, hogy ez a függvény bizonyos időközönként (a példában 6 másodpercenként) újra meg újra lefusson.
const slider = () => {
/*...*/
setTimeout(slider, 6000);
};
A függvény utolsó utasításaként szerepel ez az utasítás:
setTimeout(slider, 6000);
Ez azt eredményezi, hogy a függvény 6 másodpercenként lefusson, viszont idáig csak akkor jut el a vezérlés (vagyis ez csak akkor lesz érvényes), ha a függvény egyszer már lefutott eddig az utasításig. Éppen ezért ugyanez az utasítás a függvényen kívül is szerepel. Ez a függvényen kívüli utasítás tekinthető nyugodtan a függvény (első) meghívásának.
Most tekintsük a függvény utasításait.
Először a document.querySelector() tagfüggvénnyel megkeressük a képet, és elmentjük egy változóba, hogy később műveleteket tudjunk vele végezni.
const actual_slider_img_src = document.querySelector("#image_slider img");
Az actual_slider_img_src nevű változóba elmentjük az aktuális kép elérési útját és nevét:
const actual_slider_img_src = actual_slider_img.src;
Ennek eredményeként valami ilyesmi kerül az actual_slider_img_src nevű változóba:
https://m.blog.hu/it/itkezdoknek/image/metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg
Ezt fel fogjuk darabolni, két okból. Egyrészt azért, hogy megkapjuk a kép nevét külön, másrészt azért, hogy a kép előtti elérési utat is külön megkapjuk.
A feldarabolást a split tagfüggvénnyel végezzük el (JavaScriptben minden string típusú változónak, értéknek van split tagfüggvénye), és a / jelek mentén daraboljuk fel a szöveget.
(Persze felmerülhet az az ötlet, hogy miért nem felezzük csak el a szöveget, és az elérési utat beletesszük egy string típusú változóba, a kép nevét pedig egy másik string típusú változóba. Azért nem így csináltam, mert ha például nem a szerverre feltöltve használom a kódot, akkor az elérési út eltérő, és azt szerettem volna, ha úgy is működne a kód).
Ennek az utasításnak tehát...
const split_actual_slider_img_src = actual_slider_img_src.split("/");
Egy stringeket tartalmazó tömb az eredménye, amit a split_actual_slider_img_src nevű változóba mentünk el. A tömböt valahogy így lehet elképzelni (ez a konkrét példa épp nem szerverre feltöltve volt kipróbálva, vagyis localhoston):
['http:', '', 'localhost' , 'images', 'header', 'metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg']
Tehát a tömb elemeként olyan szövegek szerepelnek, amiket / jelek választottak el az eredeti szövegben.
Ennek a tömbnek az utolsó eleme tartalmazza a kép nevét.
Egy tömb utolsó elemére így hivatkozhatunk:
array_example[array_example.length-1]
Ugye mivel a tömb első elemének indexe 0, ezért az utolsó elemének indexe elemszám-1.
Ezzel az utasítással...
const actual_slider_img_name /* without full path */ = split_actual_slider_img_src[split_actual_slider_img_src.length-1];
... az actual_slider_img_name nevű változóba elmentjük az első kép esetén ezt a szöveget:
metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg
A függvény itt persze nem mindig az első kép nevét adja vissza, hanem annak a nevét, amire legutoljára kicseréltük.
Ezzel az utasítással...
const index_of_actual_slider_img = slider_images_array.findIndex( array_element => array_element.filename === actual_slider_img_name );
Az index_of_actual_slider_img nevű változóba megkapjuk, hogy az éppen aktuálisan betöltött kép neve hol szerepel a függvényen kívül létrehozott slider_images_array nevű tömbben, vagyis ennek az indexét kapjuk meg, ami az első kép esetén 0, az utolsó kép esetén itt ebben a példában 4, mivel itt csak 5 kép van, 5 elem esetén az utolsó elem indexe 4, vagyis elemszám-1, azaz 5-1.
Ez az index ahhoz kell, hogy a képeket tartalmazó tömbben a soron következő képet úgy érjük el, hogy ehhez az indexhez hozzáadunk egyet (illetve ha a tömbben lévő utolsó kép az aktuális kép, akkor azután a 0 indexűt, azaz az első képet töltjük be).
Az eddigi utasításokkal megszereztük azokat az információkat, amikre az aktuálisan betöltött képről szükségünk van.
A következő utasításokban létrehozunk két string típusú változót, amibe majd a következő betöltésre kerülő kép elérési útját és nevét fogjuk beletenni.
let next_img_url = "";
let next_img_alt = "";
A split_actual_slider_img_src nevű változóban van egy ilyesmi tömbünk:
['http:', '', 'localhost' , 'images', 'header', 'metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg']
Ha ebből az utolsó elemet leszámítva az összes elemet felhasználjuk, akkor megkapjuk az elérési utat.
String összefűzéssel (konkatenációval) oldjuk meg, hogy a next_img_url nevű változóhoz hozzáfűzzük ennek a tömbnek a soron következő elemét, és minden elem után írunk egy / jelet.
Tömböket a legtöbbször for ciklussal járunk be, de itt elég a tömb utolsó előtti eleméig menni, így a ciklusfeltétel i <= split_actual_slider_img_src.length-2 lesz.
for (let i = 0; i <= split_actual_slider_img_src.length-2; ++i) { next_img_url += split_actual_slider_img_src[i]; next_img_url += "/"; }
Ha ez a for ciklus lefutott, a next_img_url nevű változó tartalma a jelenlegi példakód szerint ez lesz:
https://m.blog.hu/it/itkezdoknek/image/
Ehhez már csak a következő kép fájlnevét kell hozzáfűzni. Ennél csak annyi trükk van, hogyha a képneveket tartalmazó tömbben az utolsó kép az aktuálisan betöltött kép, akkor a következő kép, a tömbben szereplő első képnév lesz, egyébként pedig a tömbben a következő kép.
Ebben a példában ahhoz, hogy ezt eldöntsük, az indexeket hasonlítjuk össze. Vagyis ha az aktuális kép a képneveket tartalmazó tömbben az utolsó indexű elem...
if (index_of_actual_slider_img === slider_images_array.length-1) {
...akkor a következő kép elérési útjához a képneveket tartalmazó tömbből az első (0 indexű) kép nevét fűzzük hozzá:
next_img_url += slider_images_array[ 0 ].filename;
Ugyanígy járunk el a következő kép alt attribútumával is:
next_img_alt = slider_images_array[ 0 ].alt;
Egyébként (vagyis valójában index_of_actual_slider_img ==! slider_images_array.length-1 feltétel esetén) pedig a következő (vagyis eggyel nagyobb) indexű kép nevét fűzzük hozzá a next_img_url nevű változóhoz:
next_img_url += slider_images_array[ index_of_actual_slider_img + 1 ].filename;
Ezt követően már csak annyi a dolgunk, hogy a next_img_url nevű változóba szövegösszefűzéssel előállított értéket értékül adjuk az actual_slider_img nevű változónak, vagyis az egyes attribútumokat az actual_slider_img megfelelő adattagjainak adjuk értékül:
actual_slider_img.src = next_img_url;
actual_slider_img.alt = next_img_alt;
actual_slider_img.title = next_img_alt;
Tulajdonképpen ezeknek az utasításoknak az eredményeképp töltődik be az új kép a régi helyére.
Ezzel a módszerrel persze nem használhatunk animációt a két kép cseréje között. Ebben a sliderben az egyszerűség kedvéért ezzel nem is foglalkoztam. Ezt például úgy szokták megoldani, hogy két képet töltenek be egyszerre, amit egymásra tesznek, de az egyik kép átlátszósága (opacity) 0, ami folyamatosan növekszik, míg a másik kép átlátszósága csökken.
<!-- Image slider -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Image slider</title>
<script>
document.addEventListener("DOMContentLoaded", () => {
//array of slider images with alt texts
//(possible extension in the future: links on images to their place in gallery page)
const slider_images_array = [
{filename: "metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg", alt: "Széll Kálmán tér, fortepan 222706", link: ""}, /* 01 */
{filename: "metros_hu_slider_image_02_keleti_palyaudvar_fortepan.jpg", alt: "Keleti pályaudvar, fortepan 11100", link: ""}, /* 02 */
{filename: "metros_hu_slider_image_03_pillango_utca_fortepan.jpg", alt: "Pillangó utca, fortepan 222698", link: ""}, /* 03 */
{filename: "metros_hu_slider_image_04_kobanya_kispest_kelecsenyi.jpg", alt: "Kőbánya-Kispest, Kelecsényi Zoltán", link: ""}, /* 04 */
{filename: "metros_hu_slider_image_05_ganz_hunslet_g2_kelecsenyi.jpg", alt: "Ganz-Hunslet G2, Kelecsényi Zoltán", link: ""}, /* 05 */
];
const slider = () => {
//get the DOM object of actual slider image
const actual_slider_img = document.querySelector("#image_slider img");
//get the full url:
//result is something like this:
//https://metros.hu/images/header/metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg
const actual_slider_img_src = actual_slider_img.src;
//put the words separated by / into an array
//result is something like this:
//['http:', '', 'localhost' , 'images', 'header', 'metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg']
const split_actual_slider_img_src = actual_slider_img_src.split("/");
//get the last element from this array
//result is a filename like this:
//metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg
const actual_slider_img_name /* without full path */ = split_actual_slider_img_src[split_actual_slider_img_src.length-1];
//get the index of current image in the array "slider_images_array"
//result is a number, for example, in the case of first image 0
const index_of_actual_slider_img = slider_images_array.findIndex( array_element => array_element.filename === actual_slider_img_name );
//string of the next image's path will be concatenated into "next_img_url" variable
let next_img_url = "";
let next_img_alt = "";
//from the array "split_actual_slider_img_src" concatenate elements except the last one to "next_img_url" variable
//and after each one, add a slash
//result is: https://metros.hu/images/header/
for (let i = 0; i <= split_actual_slider_img_src.length-2; ++i) {
next_img_url += split_actual_slider_img_src[i];
next_img_url += "/";
}
/* if the last image is loaded, load the first image after it */
//result in "next_img_url" variable: "https://metros.hu/images/header/" + the name of first image in array "slider_images_array"
//result in "next_img_alt" variable: "alt" data member / property of the first image from the array "slider_images_array"
if (index_of_actual_slider_img === slider_images_array.length-1) {
next_img_url += slider_images_array[ 0 ].filename;
next_img_alt = slider_images_array[ 0 ].alt;
/* else load the next image (current index + 1) */
//result in "next_img_url" variable: "https://metros.hu/images/header/" + the name of next image in array "slider_images_array"
//result in "next_img_alt" variable: "alt" data member / property of next image from the array "slider_images_array"
} else {
next_img_url += slider_images_array[ index_of_actual_slider_img + 1 ].filename;
next_img_alt = slider_images_array[ index_of_actual_slider_img + 1 ].alt;
}
//change current image to next image
actual_slider_img.src = next_img_url;
actual_slider_img.alt = next_img_alt;
actual_slider_img.title = next_img_alt;
//run slider() 6 secs after slider() ended
setTimeout(slider, 6000);
};
//run slider() for the first time after 6 secs
setTimeout(slider, 6000);
});
</script>
</head>
<body>
<h1>Image slider</h1>
<div id="image_slider">
<img src="https://m.blog.hu/it/itkezdoknek/image/metros_hu_slider_image_01_szell_kalman_ter_fortepan.jpg" alt="Széll Kálmán tér, fortepan 222706" title="Széll Kálmán tér, fortepan 222706">
</div>
</body>
</html>
getAttribute(), setAttribute()
A getAttribute() és setAttribute() tagfüggvények bemutatásához gyakori példa a jelszó megjelenítése és elrejtése.
A példa egyszerű, nem nagyon lehet túlmagyarázni. Egy gombhoz hozzárendelünk egy click eseményt...
document.querySelector("button#btn_passwd_toggle").addEventListener("click", () => {/*...*/});
...amiben egy <input> tag type attribútumának értékét kérdezzük le getAttribute() tagfüggvénnyel.
const input_passwd_type = input_passwd_dom.getAttribute("type");
Ha ez az érték "password"...
if (input_passwd_type == "password") {
...akkor ezt az értéket átállítjuk "text"-re (másképp fogalmazva a "text" string literált adjuk neki értékül).
input_passwd_dom.setAttribute("type", "text");
Illetve a gomb feliratát átállítjuk "Hide password"-re (jelszó elrejtésére).
Ha pedig az input mező type attribútumának értéke "text"...
} else if (input_passwd_type == "text") {
...akkor átállítjuk "password"-re, és gomb feliratát "Show password"-re (jelszó megjelenítésére) módosítjuk.
input_passwd_dom.setAttribute("type", "password");
btn_passwd_toggle_dom.innerText = "Show password";
<!-- getAttribute(), setAttribute(), show/hide password -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World in Javascript example</title>
<style>
button, input, label { display: block; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.querySelector("button#btn_passwd_toggle").addEventListener("click", () => {
const btn_passwd_toggle_dom = document.querySelector("#btn_passwd_toggle");
const input_passwd_dom = document.querySelector("input#passwd");
const input_passwd_type = input_passwd_dom.getAttribute("type");
if (input_passwd_type == "password") {
input_passwd_dom.setAttribute("type", "text");
btn_passwd_toggle_dom.innerText = "Hide password";
} else if (input_passwd_type == "text") {
input_passwd_dom.setAttribute("type", "password");
btn_passwd_toggle_dom.innerText = "Show password";
}
});
});
</script>
</head>
<body>
<h1>Show/hide password</h1>
<label for="passwd">Test a password text here:</label>
<input type="password" id="passwd" value="12345">
<button id="btn_passwd_toggle">Show password</button>
</body>
</html>
dom_element.classList.add("classname"), dom_element.classList.remove("classname")
Ha egy DOM elemnek egy vagy akár több tulajdonságát is módosítani akarjuk, úgy, hogy az könnyen visszavonható legyen, akkor érdemes használni a classList.add(), illetve a classList.remove() tagfüggvényeket.
A CSS osztályokat már előre létrehozzuk, és amikor egy DOM elemhez társítjuk ezt az osztályt (pl. gombra kattintással), akkor az osztályban lévő tulajdonságok azonnal érvényesek lesznek a DOM elemre (nem kell az oldalt újra betölteni).
Ebben a lottózós példában egy html táblázatban jelennek meg a lottó számok, amik közül kiválaszthatjuk, hogy milyen számokat játszunk meg, milyen számokra tippelünk, oly módon, hogy rákattintunk a táblázat egy adott cellájára. Például ha a 12-es számot szeretnénk kiválasztani, akkor a táblázatban a 12-es számot tartalmazó cellára kattintunk.
Ekkor lefut egy classList.add() tagfüggvény ami a "selected" CSS osztályt adja hozzá az adott táblázat cellához, és az adott cella háttérszíne zöldre változik. Ha mégsem ezt a számot szeretnénk választani, akkor a zöld hátterű cellára újbóli kattintással a zöld háttér eltűnik, ekkor visszavonjuk a tippet és egy classList.remove() tagfüggvény fut le.
Ha kiválasztottuk az adott lottótípusnak megfelelő tippeket (5-ös lottó esetén 5 számot, 6-os lottó esetén 6 számot), és a sorsolás gombra kattintunk, akkor is classList.add() tagfüggvény fogja módosítani a cellák háttérszínét.
Ha van egy találatunk, akkor annak piros háttere lesz (a táblázat cellája egyszerre fogja tartalmazni a "selected" és a "drawn" CSS osztályokat), ha pedig olyan számot sorsolt ki a számítógép, amit nem találtunk el, annak a háttere narancssárga lesz (ekkor csak a "drawn" CSS osztály adódik hozzá az adott táblázat cellához).
Tekintsük a JavaScript kódot.
A függvényeken kívül létrehoztunk 1 globális változót, illetve 2 halmaz típusú objektumot.
A "játékban" lehet választani, hogy 5-ös, vagy 6-os lottót játszunk. A lottery_type változóban tároljuk az éppen aktuális lottótípust (5 vagy 6 szám típusú értékeket). Ettől függ hogy hány elemet választhatunk ki a táblázatból, illetve az is, hogy hány sor és egy sorban hány szám (cella) lesz megjelenítve a táblázatban.
Ennek a változónak 5-öt adunk kezdőértékül, vagyis
A selected_numbers_arr és drawn_numbers_arr technikailag egy halmaz objektum, ami lényegében olyan mint egy tömb, csak nem szerepelhet benne azonos értékű elem többször. A nevükben szereplő arr szócska ugyan a tömb (array) rövidítése, és ez nem teljesen precíz, de ennél a példánál ez lényegtelen, a lényeg, hogy amikor a kódban ezt a nevet látjuk, akkor tudjuk, hogy egy tömbszerű adatszerkezetről van szó.
A selected_numbers_arr nevű halmazban tároljuk a kiválasztott számokat, a drawn_numbers_arr halmazban pedig a számítógép által random generált nyerőszámokat.
Felmerülhetne a kérdés, hogy miért kell tömbökben tárolni ezeket a számokat, hiszen a táblázat háttérszíneiből pontosan ki lehet nyerni őket. Habár ennél a feladatnál jó eséllyel nem okozna nagy sebességkülönbséget az, ha mindig amikor valamilyen műveletet szeretnénk végezni a felhasználó által kiválasztott tippekkel vagy a számítógép által generált nyerőszámokkal, akkor megvizsgálnánk a táblázat minden egyes celláját, és a háttérszínükből döntenénk el, hogy melyik elem tipp vagy nyerőszám, de érdemes hozzászokni ahhoz a gyakorlathoz, hogy azokat az értékeket, amikkel műveletet végzünk, nem mindig a grafikus felületről kérdezzük le, hanem érdemes lehet őket eltárolni, mert egy tömbbel sokkal gyorsabb műveletet végezni mint egy táblázaton végigmenni és az egyes cellák CSS stílusa alapján kiválogatni a cellák értékeit.
init() függvény
Az init függvényben lévő utasítások az oldal betöltődésekor (akár úgy is lehetne fogalmazni, hogy a program indításakor) hajtódnak végre, és csak egyszer kell őket végrehajtani, addig, amíg az oldal be van töltődve a böngészőben.
Az "új játék" és "sorsolás" feliratú gombokhoz egy-egy függvényt rendelünk hozzá, egyrészt azért, mert sokkal könnyebb így átlátni a kódot, másrészt azért, mert a new_game() függvényt nem csak akkor hajtjuk végre, amikor az "új játék" feliratú gombra kattintunk.
A rádiógombokra vonatkozó kifejezéseket tekintsük át részkifejezésenként. Ugyebár a rádiógombokat a name attribútummal szervezzük egy csoportba, ezzel oldjuk meg, hogy közülök csak egyet lehessen kiválasztani, egyszerre többet ne. Ezzel a kifejezéssel kiválasztjuk az egy csoportba tartozó rádiógombokat:
document.querySelectorAll('input[name="lottery_type"]')
Ez a kifejezés egy NodeList típusú tömbszerű adatszerkezetet ad vissza, még akkor is, ha csak egy elem lett kiválasztva.
Ennek a tömbszerű adatszerkezetnek van forEach tagfüggvénye:
forEach(element => { element.addEventListener('change', () => {
Ez a kifejezés azt jelenti, hogy a NodeList minden eleméhez hozzárendelünk egy eseménykezelőt, a "change" eseményhez.
A change esemény annyiban különbözik a click eseménytől a rádiógombok esetén, hogy a kapcsos zárójelek között megadott utasítások csak akkor futnak le, ha tényleg megváltozik az, hogy melyik rádiógomb van kiválasztva. Ezzel szemben a click esemény akkor is lefut, ha a már kiválasztott rádiógombra kattintunk.
Mivel a forEach az összes rádiógombon végigiterál, valahogy el kell dönteni, hogy melyik van közülük kiválasztva, ezt a következő kifejezéssel lehet eldönteni:
if (element.checked) {
Ekkor a kiválasztott rádiógomb value attribútumának értékét adjuk értékül a lottery_type globális változónak. De mivel az összes html attribútum értéke string típusú, ezért át kell konvertálni szám típusúvá, például a parseInt függvénnyel:
lottery_type = parseInt(element.value);
Ez persze csak akkor lehetséges, ha a rádiógomboknak megadunk value attribútumot, például a hatoslottó rádiógombjának a value="5"-öt a html kódban:
<input type="radio" name="lottery_type" id="lottery_6" value="6">
Én ebben a példában úgy döntöttem, hogy amikor kiválasztunk egy másik rádiógombot, azt követően lefuttatjuk a new_game függvényt, vagyis a táblázat újra ki fog rajzolódni, de már az újonnan kiválasztott lottótípus szerint.
Itt egyébként az a hibalehetőség, hogyha nem rajzolnánk ki újra a táblázatot, akkor például az 5-ös lottó táblázatából lehetne 6 számot kiválasztani, vagy a 6-os lottó táblázatából 5-öt. Ezt például úgy is ki lehetne küszöbölni, hogy a rádiógomboknak nem adnánk eseménykezelőt, hanem a new_game függvény lefutásakor kérdeznénk le a kiválasztott rádiógomb value attribútumának értékét.
Az init() függvényben meghívjuk a new_game() függvényt, aminek köszönhetően az oldal betöltődésekor alapból kirajzolódik egy táblázat. Mivel a lottery_type globális változó kezdőértéke 5 így az 5-ös lottó táblázata fog kirajzolódni az oldal betöltődésekor.
new_game() függvény
A függvény elején "visszacsináljuk" az előző játékhoz kapcsolódó dolgokat. Vagyis... a halmazokat (tömbszerű adatszerkezeteket) ürítjük, illetve ha a sorsolás gomb le volt tiltva, akkor annak a tiltását feloldjuk. Ezek az utasítások persze akkor is lefutnak, ha nem volt előző "játék", vagyis az oldal böngészőbe való első betöltődésekor vagy az oldal újratöltésekor, de ez nem baj, mert ezek a műveletek elenyészően lassítják a kód futását, illetve nem rontanak el semmit.
selected_numbers_arr.clear();
drawn_numbers_arr.clear();
document.querySelector("#btn_draw").disabled = false;
Ezen kívül pedig a new_game() függvény meghívja a render_table() függvényt, ami kirajzolja a táblázatot. A lottery_type változó értékétől függően: 5-ös lottó esetén 9 sor, 10 oszlop, 6-os lottó esetén 5 sor, 9 oszlop.
A sorok és oszlopok számát szám literálként átadjuk a render_table() függvénynek, például így:
render_table(9,10);
render_table() függvény
A render_table() függvényt mindig a new_game() függvény hívja meg, és a new_game() függvényből kapja a render_table() függvény a num_of_rows és num_of_cols paramétereket, amik meghatározzák, hogy 5-ös vagy 6-os lottó esetén hány sort és hány oszlopot kell kirajzolni.
A tábla kirajzolása
Itt most a változatosság kedvéért nem createElement és appendChild tagfüggvényekkel rakunk össze egy táblázatot, hanem egy stringbe felépítjük a táblázatot html tagekkel, majd ezt a stringet a táblázatot tartalmazó div innerHTML-jének adjuk értékül a végén.
Két dologra felhívnám a figyelmet. Egyrészt ahhoz, hogy a táblázat egyes celláiba számokat növekvő sorrendbe írjuk, úgy, hogy azok az egyes sortörések után is folytatólagosak legyenek, ki kell matekozni, hogy ez hogyan lehetséges:
let numberof_cell = i * num_of_cols + j;
Itt ahhoz hasonlaóan kell gondolkodni, mint amikor 10-es számrendszerbeli számok helyiértékeit számoljuk ki. Például: 142 = 2*100 + 4*101 + 1*103
A j ciklusváltozó tartalmazza, hogy egy aktuális sorban hanyadik oszlopnál tartunk. Ehhez hozzá kell adni azt, hogy hanyadik sornál tartunk (i ciklusváltozó) amit megszorzunk azzal, hogy egy sorban hány szám van (num_of_cols változó).
A num_of_cols változó tartalmazza azt, hogy egy sorban hány darab szám jelenik meg. Ez az ötös lottó esetén 10, a hatos lottó esetén 9.
Egy apró trükk, hogy az i 0-tól indul (num_of_rows-1-ig), a j viszont 1-től indul (num_of_cols-ig, beleértve azt is). Azért jó, hogy az i 0-tól indul, mert így az első sor esetén az i*num_of_cols szorzat 0-ra jön ki, és így egyszerűbb képlettel meg lehet oldani, hogy az első sorban csak a j aktuális értéke határozza meg a cellába írt számokat.
Persze úgy is meg lehetne oldani, hogy az i ciklusváltozó is 1-től megy, akkor viszont a képlet így nézne ki:
let numberof_cell = (i-1)* num_of_cols + j;
Mert ha az i 1-től megy, akkor másképp nem igazán tudjuk megoldani azt, hogy az első cellába 1-et írjunk, ne 10-et vagy 11-et.
A másik amiről érdemes szót ejteni, hogy minden cellának id-ként megadjuk a cella sorszámát (vagyis ezt a számot, amit az előző képlettel kiszámoltunk). Erre csak azért van szükség, hogy ha a számítógép kisorsolja a nyerőszámokat, akkor ne kelljen az egész táblázaton végigmenni, és megnézni, hogy az éppen aktuális cella nyerőszámot tartalmaz-e, hanem querySelectorral ki lehet választani a nyerőszámokat az id-juk alapján.
table_stringbuilder += `<td id='cell_${numberof_cell}'>${numberof_cell}</td>`;
Eseménykezelők a táblázathoz
Ahhoz, hogy a táblázatból ki tudjunk választani az általunk tippelt számokat, úgy, hogy a táblázat egyes celláira kattintunk, eseménykezelőt kell használnunk. Viszont amikor egy html tagen belül sok html tag van, akkor érdemes event delegationt használni.
Ez úgy működik, hogy az eseménykezelőt a szülőelem (parentElement) kapja meg (jelen esetben a tbody), és a click eseményen belül csak akkor hajtjuk végre az utasításokat, ha a kattintás egy gyerekelem területén történt (a gyerekelem jelen példában a td).
document.querySelector("tbody").addEventListener("click", (event) => {
if (event.target.tagName === "TD") {
Fontos, hogy a TD-t, csupa nagybetűkkel írjuk.
Azt, hogy a függvény további utasításai egyes esetekben ne kerüljenek végrehajtásra, egy iffel és egy return kulcsszóval oldjuk meg. Például ha vannak kisorsolt nyerőszámok, akkor ne lehessen a tippeket módosítani, azt így oldjuk meg:
if (drawn_numbers_arr.size != 0) { return; }
Hogyan oldjuk meg, hogy az 5-ös lottónál ne lehessen 5 tippnél többet kiválasztani, a 6-os lottónál pedig 6-nál többet?
Először is lementjük egy változóba, hogy az adott cella, amire rákattintottunk, az tippként van-e megjelölve (ennek a változónak az értékét később is felhasználjuk majd, nem csak itt):
const is_selected = event.target.classList.contains("selected");
Aztán pedig, ha a cella, amire rákattintottunk nem tipp és egyúttal igaz az, hogy a selected_numbers_arr nevű halmazban már van 5 vagy 6 elem, akkor a függvény további utasításai nem futnak le.
if (!is_selected && selected_numbers_arr.size >= lottery_type) { return; }
Egyébként az a kód, ami ebben az eseménykezelőben lefut, ha a függvény végrehajtása túljut ezeken az ellenőrzéseken, mindössze annyit csinál, hogy ha eddig az adott cella nem volt kiválasztva, akkor ki lesz választva ("selected" osztályt rendelünk hozzá, illetve a cellában lévő számértéket beletesszük a selected_numbers_arr halmazba
if (!is_selected) { event.target.classList.add("selected"); selected_numbers_arr.add(td_value); }
Ha pedig ki volt válaszva a cella (zöld háttérszíne volt), akkor a "selected" CSS osztályt töröljük hozzá kapcsolódóan, és az értékét kivesszük a selected_numbers_arr halmazból:
else { event.target.classList.remove("selected"); selected_numbers_arr.delete(td_value); }
draw() függvény
A draw() függvény csak akkor futhat le, ha a sorsolás gombra kattintunk, és egy játékban csak egyszer.
Ha nincs kiválasztva 5-ös lottó esetén 5 tipp, vagy 6-os lottó esetén 6, akkor a függvény return-öl, azaz a további utasítások nem kerülnek végrehajtásra. Előtte kiírunk egy felugró üzenetet, hogy tájékoztassuk a felhasználót, hogy miért nem történik meg a sorsolás.
if (selected_numbers_arr.size < lottery_type) {
alert(`Kérem válasszon ki ${lottery_type} számot a sorsoláshoz!`);
return; }
A sorsolás akkor sem történik meg, ha egyszer már megtörtént, de még nem lett elindítva új játék:
if (drawn_numbers_arr.size != 0) { return; }
A sorsoláshoz használt random szám generáló függvény már korábbi példákból ismert lehet, itt annyi a különbség, hogy a draw() függvényen belül definiáltuk, így csak a draw() függvény tudja használni. Ezt függvények egymásba ágyazásának nevezik (nested functions), és akkor érdemes használni, ha tudjuk, hogy egy függvényt csak egy adott függvényen belül hívunk meg, másik függvényben nem.
const random_number = (min, max) => { return (Math.floor(Math.random() * (max - min + 1) ) + min); };
Ezt követően beállítjuk, hogy az egyes lottótípusok esetén milyen számok között legyen a sorsolás (5-ös lottó: 1 és 90 között, 6-os lottó 1 és 45 között):
let min = 0; let max = 0;
switch (lottery_type) {
case 5:
min = 1; max = 90; break;
case 6: min = 1; max = 45; break;
}
Majd annyi számot sorsolunk ki, ahány az adott lottótípusnál megfelelő:
while (drawn_numbers_arr.size != lottery_type) { drawn_numbers_arr.add(random_number(min, max)); }
Végül pedig beszínezzük a nyerőszámok celláit:
drawn_numbers_arr.forEach((element, index) => { document.querySelector(`#cell_${element}`).classList.add("drawn"); });
Érdekesség, hogy nem a táblázaton megyünk végig, hanem a nyerőszámokat tartalmazó tömbön. A táblázatból pedig cella id alapján ki tudjuk választani pontosan az adott számokat.
Egyébként ha a táblázaton mennénk végig, az valahogy így nézne ki:
/*document.querySelectorAll(".table_container:first-of-type tbody td").forEach((element, index) => { if (drawn_numbers_arr.has(parseInt(element.textContent))) { element.classList.add("drawn"); } });*/
Ezt a kódrészletet benne hagytam kikommentelve a kódban, de csak akkor működne, ha a felette lévő megoldást kikommentelnénk.
A teljes példakód:
A példakód több ponton le lett egyszerűsítve a bejegyzés karakterlimitje miatt. Például meg lehetne oldani, hogyha az új játék gombra kattintunk, ne rajzolódjon ki újra a táblázat abban az esetben, ha nem választunk ki eltérő lottótípust, hanem csak törlődjenek a cellák "selected" illetve "drawn" CSS osztályai, illetve a tippeket és nyerőszámokat tartalmazó halmazok ki legyenek ürítve.
<!-- lotto5, lotto6 example -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lottery</title>
<style>
button {
display: inline-block;
padding: 5px;
font-size: 1em;
}
#btn_new_game { background-color: lime; }
#btn_draw { background-color: orange; }
.table_container {
overflow-x: auto;
width: 100%;
}
table {
width: 100%;
text-align: center;
padding: 0.3em;
}
thead tr {
color: white;
background-color: #1e3a8a;
}
tr { padding: 0.3em; }
table tbody tr:nth-of-type(even) { background-color: #e0f2fe; }
table tbody tr:nth-of-type(odd) { background-color: #bfdbfe; }
table td:hover {
color: black;
background-color: yellow;
}
th, td { padding: 0.3em; }
td.selected { background-color: lime; }
td.drawn { background-color: orange; }
td.selected.drawn { background-color: orangered; }
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
//init vars
let lottery_type = 5;
let selected_numbers_arr = new Set();
let drawn_numbers_arr = new Set();
//init eventlisteners
//this function should be called only once, when the browser loads this file
const init = () => {
document.querySelector("#btn_new_game").addEventListener("click", () => {
new_game();
});
document.querySelector("#btn_draw").addEventListener("click", () => {
draw();
});
document.querySelectorAll('input[name="lottery_type"]').forEach(element => {
element.addEventListener('change', () => {
if (element.checked) {
lottery_type = parseInt(element.value);
new_game();
}
});
});
new_game();
}
const new_game = () => {
selected_numbers_arr.clear();
drawn_numbers_arr.clear();
document.querySelector("#btn_draw").disabled = false;
if (lottery_type == 5) {
render_table(9,10);
} else if (lottery_type == 6) {
render_table(5,9);
}
}
const render_table = (num_of_rows, num_of_cols) => {
let table_stringbuilder = "";
table_stringbuilder += "<table>";
table_stringbuilder += "<thead>";
table_stringbuilder += "<tr>";
table_stringbuilder += `<td colspan='${num_of_cols}'>Lottó</td>`;
table_stringbuilder += "</tr>";
table_stringbuilder += "</thead>";
table_stringbuilder += "<tbody>";
for (let i = 0; i < num_of_rows; ++i) {
table_stringbuilder += "<tr>";
for (let j = 1; j <= num_of_cols; ++j) {
let numberof_cell = i * num_of_cols + j;
table_stringbuilder += `<td id='cell_${numberof_cell}'>${numberof_cell}</td>`;
}
table_stringbuilder += "</tr>";
}
table_stringbuilder += "</tbody>";
table_stringbuilder += "</table>";
//table completed
document.querySelector("div.table_container").innerHTML = table_stringbuilder;
//make table cells selectable with event delegation
document.querySelector("tbody").addEventListener("click", (event) => {
if (event.target.tagName === "TD") {
//if there are drawn numbers, selected element cannot be changed
if (drawn_numbers_arr.size != 0) {
return;
}
const is_selected = event.target.classList.contains("selected");
//cannot select more cells than lottery type (5 or 6)
if (!is_selected && selected_numbers_arr.size >= lottery_type) {
return;
}
const td_value = parseInt(event.target.textContent);
//invert selection
if (!is_selected) {
event.target.classList.add("selected");
selected_numbers_arr.add(td_value);
} else {
event.target.classList.remove("selected");
selected_numbers_arr.delete(td_value);
}
}
});
}
const draw = () => {
Mivel számok random
//select 5 or 6 cells to be able to draw
if (selected_numbers_arr.size < lottery_type) {
alert(`Kérem válasszon ki ${lottery_type} számot a sorsoláshoz!`);
return;
}
//if there are drawn numbers, game should be restarted to make another draw
if (drawn_numbers_arr.size != 0) {
return;
}
const random_number = (min, max) => {
return (Math.floor(Math.random() * (max - min + 1) ) + min);
};
//make 5 or 6 random numbers
let min = 0; let max = 0;
switch (lottery_type) {
case 5:
min = 1; max = 90; break;
case 6:
min = 1; max = 45; break;
}
while (drawn_numbers_arr.size != lottery_type) {
drawn_numbers_arr.add(random_number(min, max));
}
//change the color of drawn numbers in the table
drawn_numbers_arr.forEach((element, index) => {
document.querySelector(`#cell_${element}`).classList.add("drawn");
});
/*document.querySelectorAll(".table_container:first-of-type tbody td").forEach((element, index) => {
if (drawn_numbers_arr.has(parseInt(element.textContent))) {
element.classList.add("drawn");
}
});*/
document.querySelector("#btn_draw").disabled = true;
}
//when the browser loads this file, init() function called
init();
});
</script>
</head>
<body>
<div class="table_container"></div>
<div class="table_container">
<table>
<thead>
<tr>
<td colspan="3">Jelmagyarázat</td>
</tr>
</thead>
<tbody>
<tr>
<td style="background-color:lime">Tipp</td>
<td style="background-color:orange">Találat</td>
<td style="background-color:orangered">Eltalált</td>
</tr>
</tbody>
</table>
<input type="radio" name="lottery_type" id="lottery_5" value="5" checked>
<label for="lottery_5" >5-ös lottó</label>
<input type="radio" name="lottery_type" id="lottery_6" value="6">
<label for="lottery_6">6-os lottó</label>
<br>
<button id="btn_new_game">Új játék</button>
<button id="btn_draw">Sorsolás</button>
</div>
</body>
</html>
A példakód itt is kipróbálható:
Folytköv...
input value
input change
input validation
window resize
document keypress
legördülő menü
br, esetleg pre
Lapozó
https://www.youtube.com/watch?v=b9IJPKh0lTg
Egyéb tananyag
- Gremmédia - Javascript
- nyelvek.inf.elte.hu - Javascript, DOM és HTML
- developer.mozilla.org - DOM
- www.fullstackfoundations.com - HTML DOM
előző tananyagrész: CSS
következő tananyagrész: PHP