Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 18 - 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 nahoru").onclick=function () {
        pridejRadek(false); // parametr nahoru nastavíme na true
};

vytvorTlacitkoAVlozHo("Přidat řádek dolů").onclick=function () {
        pridejRadek(true); // parametr nahoru nastavíme na false
};

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:

Editor tabulek
localhost

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:

Editor tabulek
localhost

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 1795x (2.11 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

Předchozí článek
Editor tabulek v JavaScriptu
Všechny články v sekci
Základní konstrukce jazyka JavaScript
Přeskočit článek
(nedoporučujeme)
Striktní operátory a přetypování v JavaScriptu
Článek pro vás napsal Michal Žůrek - misaz
Avatar
Uživatelské hodnocení:
702 hlasů
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Aktivity