Lekce 21 - Dokončení editoru tabulek v JavaScriptu
V minulé lekci, Editor tabulek v JavaScriptu, jsme rozpracovali editor HTML tabulek, který uživateli umožňuje tabulku 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 pozici této aktivní buňky. Vytvoříme si tedy dvě
funkce, které později využijeme. První 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>
Tabulka je reprezentována elementem <table>.
Řádky (<tr>) jsou jejími
přímými potomky. Každý řádek má zase
jako přímé potomky jednotlivé buňky
(<td>). Na začátku funkce
indexRadkuAktivniBunky() budeme mít tento kód:
function indexRadkuAktivniBunky() { let uzlyTabulky = tabulka.childNodes; let hledanyRadek = aktivniBunka.parentElement.parentElement; // Zde doplníme kód pro získání indexu hledaného řádku }
Do proměnné uzlyTabulky vkládáme pomocí vlastnosti
childNodes všechny přímé potomky elementu tabulky. Získáme
tak kolekci jednotlivých řádků (elementů
<tr> ) tabulky. Kolekce je typu NodeList a my v
ní budeme hledat řádek obsahující aktivní buňku.
Hledaný řádek získáme z aktivní buňky pomocí vlastnosti
parentElement. Protože máme v proměnné
aktivniBunka uložený element <input>, vrátí
nám vlastnost aktivniBunka.parentElement jako nadřazený element
příslušnou buňku (<td>), ve které je
<input> vnořený. Abychom se dostali k řádku (elementu
<tr>), ve kterém se aktivní buňka nachází, je nutné
zavolat parentElement dvakrát.
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á vrací kolekci
obsahující pouze HTML elementy. Uzel je základní jednotka DOM, která může
představovat element, text nebo komentář. HTML element je specifický typ
uzlu, který vždy odpovídá konkrétní značce/tagu v HTML.
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ž se 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. Ukážeme si dva
způsoby, jak tento problém vyřešit.
Převod kolekce NodeList
na pole
Nejprve uložíme naši kolekci do nového pole pomocí metody
Array.from(). Poté na tomto poli zavoláme metodu
indexOf() pro získání požadovaného indexu:
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 vytváří duplicitní kolekci uzlů ve formě pole. To bývá nevhodné z hlediska efektivity, protože zbytečně spotřebovává paměť a zvyšuje výpočetní náročnost.
Změna kontextu metody
indexOf()
Další řešení představuje možnost zavolat metodu indexOf()
na poli, ale "podvrhnout" mu NodeList. JavaScriptu na typu objektu
nezáleží, pokud má potřebné vlastnosti a strukturu. Tento přístup se
nazývá volání s upraveným kontextem. Upravený kód
vypadá takto:
function indexRadkuAktivniBunky() { let uzlyTabulky = tabulka.childNodes; let hledanyRadek = aktivniBunka.parentElement.parentElement; return Array.prototype.indexOf.call(uzlyTabulky, hledanyRadek); }
Metodu voláme se změněným kontextem tak, že ji
vyhledáme v prototypu objektu (v našem případě
Array.prototype) a použijeme na ní metodu call().
Té jako první parametr předáváme nový kontext a jako
druhý parametr argumenty původní metody. Jde o
složitější syntaxi, díky které jsme se však vyhnuli 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:
function indexSloupceAktivniBunky() { let uzlyRadku = aktivniBunka.parentElement.parentElement.childNodes; let hledanaBunka = aktivniBunka.parentElement; return Array.prototype.indexOf.call(uzlyRadku, hledanaBunka); }
Abychom v kolekci měli opravdu uzly vybraného řádku,
musíme vzít aktivní buňku (element <input>) a
zřetězeným voláním vlastnosti parentElement se dostat k
nadřazenému elementu <tr>. Teprve pak na něj voláme
vlastnost childNodes a získáváme kolekci jednotlivých buňek v
daném řádku. Nakonec znovu voláme metodu indexOf() se
změněným kontextem a získáváme tím index hledané buňky.
Přidávání řádků
Nyní si doplníme funkce pro obsluhu vytvořených tlačítek, začneme přidáváním řádků.
Funkce pridejRadekNahoru()
Funkce pro přidávání řádku nad vybranou buňku bude vypadat následovně:
function pridejRadekNahoru() { let novyRadek = vytvorRadek(); let indexVybraneho = indexRadkuAktivniBunky(); tabulka.insertBefore(novyRadek, tabulka.childNodes[indexVybraneho]); }
Pomocí naší již vytvořené funkce vytvorRadek()
vytváříme nový řádek a ukládáme ho do proměnné. Dále si ukládáme
index řádku, ve kterém se nachází aktivní (označená)
buňka. Nad ten pak vkládáme nově vytvořený řádek metodou
insertBefore(). Tato metoda přijímá jako první parametr
element, který má být vložen do HTML struktury, a jako druhý parametr uzel,
před který se má tento element umístit. V našem případě je to řádek,
ve kterém se nachází aktivní buňka.
Funkce pridejRadekDolu()
Doplníme funkci přidávající řádek pod vybranou buňku:
function pridejRadekDolu() { let novyRadek = vytvorRadek(); let indexVybraneho = indexRadkuAktivniBunky(); if (tabulka.lastChild == tabulka.childNodes[indexVybraneho]) { tabulka.appendChild(novyRadek); } else { tabulka.insertBefore(novyRadek, tabulka.childNodes[indexVybraneho + 1]); } }
Ve funkci máme navíc podmínku, kterou zjišťujeme, zda je vybrána buňka
na posledním řádku tabulky. Pokud tomu tak je, použijeme metodu
appendChild(), která jednoduše vloží nový řádek na konec
tabulky.
Pokud však aktivní buňka není na posledním řádku, vložíme nový
řádek za aktuálně vybraný řádek do tabulky pomocí
metody insertBefore(). K tomu stačí navýšit index
vybraného řádku o jedna. Tím se zajistí, že se nový prvek
vloží přímo za řádek obsahující aktivní buňku a ne před něj.
Nakonec se přesuneme do naší funkce vytvorOvladaciTlacitka()
a nastavíme zde obsluhu kliknutí prvním dvěma tlačítkům:
vytvorTlacitko("Přidat řádek dolů").onclick = pridejRadekDolu; vytvorTlacitko("Přidat řádek nahoru").onclick = pridejRadekNahoru;
Novou funkcionalitu si můžeme vyzkoušet v živé ukázce níže. Nezapomeňme však nejprve označit nějakou buňku tabulky kliknutím na ni:
Přidávání sloupců
Jako další vytvoříme funkce pro přidávání sloupců vlevo a vpravo. Přidávání sloupců je oproti přidávání řádků trochu složitější. Tabulka v HTML totiž obsahuje řádky, které obsahují buňky, ale sloupce jako takové v ní definovány nejsou. Přidávání sloupců tedy budeme muset řešit tak, že přidáme buňku do každého z existujících řádků. Pomocí cyklu projdeme všechny řádky tabulky a do každého řádku vložíme novou buňku buď před, nebo za označenou buňku.
Funkce
pridejSloupecDoleva()
Do našeho kódu doplníme funkci pro přidávání sloupců nalevo od aktivní buňky:
function pridejSloupecDoleva() { let indexVybraneho = indexSloupceAktivniBunky(); for (let i = 0; i < tabulka.childNodes.length; i++) { let novaBunka = vytvorBunku(); tabulka.childNodes[i].insertBefore(novaBunka, tabulka.childNodes[i].childNodes[indexVybraneho]); } }
Nejprve zjišťujeme index sloupce, ve kterém se nachází
aktivní buňka a ukládáme ho do proměnné
indexVybraneho. Následně cyklem procházíme jednotlivé řádky
tabulky. Kolik má tabulka řádků zjišťujeme zápisem
tabulka.childNodes.length. Do proměnné novaBunka
ukládáme nově vytvořenou buňku pomocí funkce
vytvorBunku().
Následně opět metodou insertBefore() tuto novou buňku
vkládáme do řádku. Místo, před které se nová buňka vkládá, určujeme
zápisem tabulka.childNodes[i].childNodes[indexVybraneho], kde
childNodes[i] nám vrací vždy ten řádek, ve kterém se
právě cyklus nachází, z něj se voláním
childNodes[indexVybraneho] dostaneme už na konkrétní
buňku, před kterou se nová bude vkládat.
Funkce
pridejSloupecDoprava()
U přidání sloupce směrem doprava budeme opět muset kontrolovat, zda se nejedná o poslední sloupec tabulky:
function pridejSloupecDoprava() { let indexVybraneho = indexSloupceAktivniBunky(); for (let i = 0; i < tabulka.childNodes.length; i++) { let novaBunka = vytvorBunku(); 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]); } } }
Do funkce pridejSloupecDoprava() jsme tedy doplnili podmínku,
abychom zjistili, zda je vybraná buňka (indexVybraneho) v
aktuálním řádku (tabulka.childNodes[i]) poslední buňkou
tohoto řádku (tabulka.childNodes[i].lastElementChild). Pokud ano,
přidáme novou buňku metodou appendChild(). V opačném
případě použijeme metodu insertBefore() a
indexVybraneho zvýšíme o jedna.
Naše čtyři funkce pro přidávání sloupců a řádků by mohly být sloučeny do dvou univerzálnějších. Tento přístup by byl z hlediska dobrých programátorských praktik, zejména zásady DRY (neopakuj se), vhodnější. Zvolili jsme však rozdělení kódu do více specifických funkcí z důvodu přehlednosti a snadnějšího nastavování událostí kliknutí pro jednotlivá tlačítka.
Ve funkci vytvorOvladaciTlacitka() nastavíme obsluhu kliknutí
dalším dvěma tlačítkům:
vytvorTlacitko("Přidat sloupec vlevo").onclick = pridejSloupecDoleva; vytvorTlacitko("Přidat sloupec vpravo").onclick = pridejSloupecDoprava;
Mazání
Nakonec do aplikace přidáme funkce pro mazání řádku a sloupce.
Mazání řádku
Funkce pro mazání řádků bude vypadat následovně:
function smazRadek() { let indexVybraneho = indexRadkuAktivniBunky(); tabulka.removeChild(tabulka.childNodes[indexVybraneho]); }
Řádek odstraňujeme pomocí metody removeChild(), která jako
parametr očekává element určený k odstranění. V našem případě jde o
řádek obsahující aktivní buňku, jehož index získáváme funkcí
indexRadkuAktivniBunky() a ukládáme ho do proměnné
indexVybraneho. Následně tuto proměnnou využíváme k výběru
správného elementu z kolekce podřízených elementů
(childNodes) tabulky.
Mazání sloupce
Nyní doplníme funkci pro mazání sloupců:
function smazSloupec() { let indexVybraneho = indexSloupceAktivniBunky(); for (let i = 0; i < tabulka.childNodes.length; i++) { tabulka.childNodes[i].removeChild(tabulka.childNodes[i].childNodes[indexVybraneho]); } }
Cyklem procházíme všechny řádky tabulky a mažeme v nich ty buňky,
jejichž index odpovídá indexu té označené. K mazání opět používáme
metodu removeChild(). Tu voláme na
tabulka.childNodes[i], tedy aktuálním řádku, ve kterém se
právě nacházíme v cyklu, a předáváme jí parametr
tabulka.childNodes[i].childNodes[indexVybraneho]. Tento parametr
označuje buňku ke smazání, tedy buňku se stejným indexem, jaký má
právě označená buňka.
Zprovozníme obsluhu posledních dvou tlačítek a máme hotovo:
vytvorTlacitko("Odstranit řádek").onclick = smazRadek; vytvorTlacitko("Odstranit sloupec").onclick = smazSloupec;
Aplikaci si můžeme vyzkoušet, opět ale musíme nejprve označit nějakou buňku 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 2291x (3.24 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript
