Lekce 19 - Dokončení editoru tabulek v JavaScriptu
V minulé lekci, Editor tabulek v JavaScriptu, jsme rozpracovali editor tabulek, který umožní uživateli tabulky rozšiřovat a upravovat.
V dnešním tutoriálu základů JavaScriptu dokončíme rozpracovaný editor tabulek. Tato část bude zaměřena především na manipulaci s elementy DOM. Do projektu totiž doplníme funkce, které nám umožní vkládat sloupce a řádky na vybrané místo tabulky. Nakonec si ukážeme, jak zajistit jejich smazání.
Zjištění pozice aktivní buňky
Z dřívějška máme v našem editoru implementovanou možnost
výběru buňky v tabulce. Do proměnné aktivniBunka
ukládáme element <input>
, který uživatel označil.
Abychom mohli tabulku upravovat na vybraném místě, budeme nejprve potřebovat
zjistit tuto pozici aktivní buňky. Vytvoříme si tedy dvě
funkce, které později využijeme. Jednou z nich bude funkce
indexRadkuAktivniBunky()
, která vrátí index řádku aktivní
buňky počínaje 0
. Druhá bude funkce
indexSloupceAktivniBunky()
, která vrátí odpovídající index
sloupce aktivní buňky.
Funkce
indexRadkuAktivniBunky()
Nejprve si vysvětlíme, jak index řádku získáme. Připomeňme si hierarchii potomků vygenerované tabulky:
<body> <table> <tr> <td> <input type="text"> </td> <td> <input type="text"> </td> <td> <input type="text"> </td> <!-- Zde jsou další dvě buňky řádku s vnořenými elementy typu input --> </tr> <tr> <!-- Stejným způsobem je vytvořen druhý a třetí řádek --> </tr> </table> </body>
Hledáme v tabulce, která je představována elementem
<table>
a řádky jsou jejími přímými
potomky. Připravíme si tedy kolekci, do které vložíme všechny
uzly, které jsou přímými potomky elementu tabulky. Pomocí vlastnosti
childNodes
volané na naší tabulce získáme takto všechny její
elementy <tr>
. V kolekci uzlů typu NodeList
budeme hledat element <tr>
odpovídající aktivní
buňce.
Protože máme v proměnné aktivniBunka
uložený element
<input>
, vrátí nám vlastnost parentElement
jako její nadřazený element odpovídající buňku tabulky
(<td>
). Abychom se dostali k odpovídajícímu
řádku (elementu <tr>
), musíme tedy
zavolat parentElement
dvakrát.
Na začátku funkce indexRadkuAktivniBunky()
budeme mít tento
kód:
function indexRadkuAktivniBunky() { let uzlyTabulky = tabulka.childNodes; // seznam všech uzlů tabulky let hledanyRadek = aktivniBunka.parentElement.parentElement; // řádek s aktivní buňkou // Zde doplníme kód pro získání indexu hledaného řádku }
Již víme, že vlastnost childNodes
zahrnuje
všechny přímo podřízené uzly rodičovského elementu. V
našem případě bychom mohli využít také vlastnost children
,
která vrátí kolekci obsahující pouze HTML elementy.
Metoda indexOf()
Abychom nemuseli procházet celou kolekci uzlů a pokaždé porovnávat jeden
její prvek s hledaným řádkem, představíme si metodu
indexOf()
. Tato metoda nám vrátí přímo index řádku s
aktivní buňkou. Voláme ji na poli, v němž má index prvku
hledat a jako parametr ji předáváme hledaný prvek.
Jedná se o metodu pro práci s polem, my ji však
potřebujeme využít na kolekci NodeList
. Ukažme si dva způsoby,
jak tento problém vyřešit.
Převod kolekce NodeList
na pole
Jako první uložíme naši kolekci do nového pole a zavoláme na něm
metodu indexOf()
:
function indexRadkuAktivniBunky() { let uzlyTabulky = tabulka.childNodes; let hledanyRadek = aktivniBunka.parentElement.parentElement; let uzlyPole = Array.from(uzlyTabulky); return uzlyPole.indexOf(hledanyRadek); }
Tento zápis je srozumitelný, ale duplikuje kolekci uzlů do pole.
Změna kontextu metody
indexOf()
Další řešení představuje možnost zavolat metodu indexOf()
na poli, ale "podvrhnout" mu NodeList
. JavaScriptu je totiž jedno,
s čím pracuje, pokud má vše potřebné.
Metody se volají se změněným kontextem tak, že je
najdeme v prototypu objektu (v našem případě Array.prototype
)
a zavoláme na ně metodu call()
. Jako první parametr jí
předáme nový kontext a za něj přidáme parametry
původní metody.
Kód výše upravíme následovně:
function indexRadkuAktivniBunky() { let uzlyTabulky = tabulka.childNodes; let hledanyRadek = aktivniBunka.parentElement.parentElement; return Array.prototype.indexOf.call(uzlyTabulky, hledanyRadek); }
Jde o složitější syntaxi, pomocí které se však vyhneme vytváření další kolekce.
Funkce
indexSloupceAktivniBunky()
Podobně budeme postupovat při získávání indexu sloupce aktivní buňky.
Vytvoříme si kolekci uzlů daného řádku a proměnnou pro
hledaný element <td>
aktivní buňky. Abychom v kolekci
měli opravdu uzly vybraného řádku, musíme vzít aktivní
buňku a zřetězeným voláním vlastnosti parentElement
se dostat
k nadřazenému elementu <tr>
. Teprve pak na něj zavoláme
vlastnost childNodes
. Nakonec znovu zavoláme metodu
indexOf()
se změněným kontextem:
function indexSloupceAktivniBunky() { let uzlyRadku = aktivniBunka.parentElement.parentElement.childNodes; let hledanaBunka = aktivniBunka.parentElement; return Array.prototype.indexOf.call(uzlyRadku, hledanaBunka); }
Z aktuální buňky (elementu <input>
) jsme se vlastností
parentElement
dostali k nadřazené buňce a z ní k
odpovídajícímu řádku. Z něj jsme pomocí childNodes
vytvořili kolekci vnořených uzlů. Hledanou buňku pak reprezentuje přímo
rodič aktivní buňky.
Přidání řádku
Nyní si doplníme funkce pro obsluhu vytvořených tlačítek. Začneme přidáním řádku.
Vytvoříme funkci pridejRadek()
, které v parametru určíme,
zda chceme vložit nový řádek nad vybraný řádek či pod něj. Parametr
pojmenujeme nahoru
a budeme mu předávat hodnoty true
nebo false
. Ve funkci zavoláme funkci vytvorRadek()
,
kterou již máme připravenou. Nový řádek si uložíme do proměnné. Dále
získáme a uložíme index řádku.
Doplníme podmínku testující parametr nahoru
, abychom
věděli, kam řádek vložit. Pro vložení nového řádku nad
vybraný řádek využijeme metodu insertBefore()
.
Připomeňme, že metoda insertBefore()
bere v prvním parametru
vkládaný prvek a ve druhém zadáváme prvek, před který chceme nový prvek
vložit.
Před vložením řádku pod vybraný řádek v další
podmínce nejprve zjistíme, zda se jedná o poslední řádek tabulky. Pokud
ano, vložíme nový řádek metodou appendChild()
. V opačném
případě použijeme metodu insertBefore()
a navýšíme
indexVybraneho
o 1
.
Celý kód vypadá takto:
function pridejRadek(nahoru) { let novyRadek = vytvorRadek(); let indexVybraneho = indexRadkuAktivniBunky(); if (nahoru) { tabulka.insertBefore(novyRadek, tabulka.childNodes[indexVybraneho]); // vloží nový řádek nad řádek s aktivní buňkou } else { if (tabulka.lastChild == tabulka.childNodes[indexVybraneho]) { tabulka.appendChild(novyRadek); // pokud je vybraný řádek poslední, použijeme metodu appendChild() } else { tabulka.insertBefore(novyRadek, tabulka.childNodes[indexVybraneho + 1]); // pokud není vybraný řádek poslední, použijeme metodu insertBefore() } } }
Nakonec ve funkci vytvorOvladaciTlacitka()
nastavíme obsluhu
kliknutí prvním dvěma tlačítkům:
vytvorTlacitkoAVlozHo("Přidat řádek dolů").onclick=function () { pridejRadek(false); // parametr nahoru nastavíme na false }; vytvorTlacitkoAVlozHo("Přidat řádek nahoru").onclick=function () { pridejRadek(true); // parametr nahoru nastavíme na true };
Protože události onclick
předáváme
funkci s parametry, museli jsme použít anonymní
funkci a do ní volání metody pridejRadek()
vložit.
Novou funkcionalitu si můžeme vyzkoušet v živé ukázce níže, nezapomeňme ovšem předtím označit nějakou buňku tabulky kliknutím:
Přidání sloupců
Jako další vytvoříme funkci pro přidávání sloupců vlevo a vpravo. Přidání sloupce je však oproti přidání řádku trochu složitější. Tabulka v HTML totiž obsahuje řádky a v těch jsou buňky. Zajímavé je že, přímo ve struktuře HTML kódu nejsou definovány sloupce jako takové. Přidání sloupce budeme muset tedy řešit tak, že přidáme buňku do každého z existujících řádků. Toho dosáhneme iterací. Cyklem projedeme všechny řádky tabulky a do každého řádku před index vybrané buňky vložíme novou buňku.
Jinak bude funkce pridejSloupec()
fungovat na podobném principu
jako funkce pridejRadek()
. Zjistíme a uložíme v ní
index sloupce aktivní buňky. Doplníme cyklus, který
proběhne tolikrát, kolik je v tabulce řádků. To zjistíme z vlastnosti
length
na tabulka.childNodes
. Na začátku cyklu
zavoláním funkce vytvorBunku()
získáme novou buňku a uložíme
ji do proměnné. V podmínce zjistíme, zda ji máme vložit
doleva nebo doprava od aktivní buňky. To určujeme v
parametru funkce podobně jako u přidávání řádku.
Novou buňku budeme vkládat postupně na každý řádek tabulky, tedy na
pozici tabulka.childNodes[i]
, kde i
obsahuje
aktuální iteraci cyklu. Vložení buňky doleva zajistíme
opět metodou insertBefore()
. Jejím prvním parametrem bude
novaBunka
. Druhý parametr určí pozici buňky, před níž chceme
novou buňku vložit. Tuto pozici získáme vyhledáním aktuálního řádku
(tabulka.childNodes[i]
), na který zavoláme
childNodes[indexVybraneho]
, abychom v něm nalezli aktivní
buňku.
Při vložení nové buňky doprava využijeme stejné
pozice. Podobně jako při vkládání řádku musíme v tomto případě
otestovat, zda se nejedná o poslední element tabulky. Pokud
ano, zavoláme metodu appendChild()
. V opačném případě
použijeme metodu insertBefore()
a navýšíme
indexVybraneho
o 1
:
function pridejSloupec(doleva) { let indexVybraneho = indexSloupceAktivniBunky(); for (let i = 0; i < tabulka.childNodes.length; i++) { let novaBunka = vytvorBunku(); if (doleva) { tabulka.childNodes[i].insertBefore(novaBunka, tabulka.childNodes[i].childNodes[indexVybraneho]); } else { if (tabulka.childNodes[i].childNodes[indexVybraneho] == tabulka.childNodes[i].lastElementChild) { tabulka.childNodes[i].appendChild(novaBunka); } else { tabulka.childNodes[i].insertBefore(novaBunka, tabulka.childNodes[i].childNodes[indexVybraneho + 1]); } } } }
Ve funkci vytvorOvladaciTlacitka()
nastavíme obsluhu kliknutí
dalším dvěma tlačítkům:
vytvorTlacitkoAVlozHo("Přidat sloupec vlevo").onclick=function () { pridejSloupec(true); }; vytvorTlacitkoAVlozHo("Přidat sloupec vpravo").onclick=function () { pridejSloupec(false); };
Mazání
Nakonec do aplikace přidáme funkce pro mazání řádku a sloupce. Mazání řádků a sloupců je důležité pro úpravu tabulky, když některé informace již nejsou relevantní nebo byly zadány omylem. Představme si, že máme například tabulku s údaji o projektu a potřebujeme odstranit řádek, který obsahuje zastaralé nebo neplatné informace o projektových úkolech.
Mazání řádku
Vybraný řádek z tabulky odstraníme pomocí metody
removeChild()
. V parametru jí tedy zadáme řádek, který chceme
smazat. Protože jde o řádek, kde se nachází aktivní buňka, získáme si
naší funkcí indexRadkuAktivniBunky()
jeho index. Tento index pak
využijeme pro výběr z podřízených elementů tabulky
(childNodes
):
function smazRadek() { let indexVybraneho = indexRadkuAktivniBunky(); tabulka.removeChild(tabulka.childNodes[indexVybraneho]); /// odstraní řádek s daným indexem }
Mazání sloupce
Mazání sloupce musíme zajistit tak, že smažeme pomocí cyklu na každém
řádku jednu buňku, jejíž index odpovídá indexu vybrané buňky. Metodu
removeChild()
budeme tedy v cyklu volat na aktuální řádek
(tabulka.childNodes[i]
) a v parametru dvakrát zavoláme vlastnost
childNodes
, poprvé s indexem cyklu, podruhé s indexem vybrané
buňky:
function smazSloupec() { let indexVybraneho = indexSloupceAktivniBunky(); for (let i = 0; i < tabulka.childNodes.length; i++) { tabulka.childNodes[i].removeChild(tabulka.childNodes[i].childNodes[indexVybraneho]); // odstraní buňku s daným indexem ve všech řádcích } }
Zprovozníme obsluhu posledních dvou tlačítek a máme hotovo:
vytvorTlacitkoAVlozHo("Odstranit řádek").onclick = smazRadek; vytvorTlacitkoAVlozHo("Odstranit sloupec").onclick = smazSloupec;
Protože poslední dvě funkce našeho editoru
nepřijímají žádný parametr, nemuseli jsme je v události
onclick
odpovídajících tlačítek volat pomocí
anonymní funkce.
Aplikaci si můžeme vyzkoušet, začneme opět kliknutím do tabulky:
Po tomto příkladu bychom již měli ovládat DOM v JavaScriptu. Všimněme si, že na začátku všeho máme HTML kód, který v body nemá jediný element.
V příští lekci, Striktní operátory a přetypování v JavaScriptu, si znovu řekneme něco o podmínkách a ukážeme si některá úskalí přetypování, než se pustíme do dalšího většího tématu - práce s grafikou.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 1829x (2.11 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript