Crearea dinamica a tabelelor HTML

Folosim WebAPI pentru crearea tabelelor HTML

Daniel Turcu

6 minute read

HTMLTableElements

Fara alte unelte la indemana (librarii externe), vom crea dinamic un tabel HTML folosind Web API (plain vanilla javascript). Pentru a manipula tabelul in DOM apelam la interfata HTMLTableElement .

HTMLTableElement -> HTMLElement -> Element -> Node


Crearea dinamica pornind de la un set de date

Consideram urmatorul array de produse:


let products = [
   { id: 1,  name: 'Product One',   category: 10, price: 7 }, 
   { id: 2,  name: 'Product Two',   category: 10, price: 3 }, 
   { id: 3,  name: 'Product Three', category: 20, price: 12 }, 
   { id: 4,  name: 'Product Four',  category: 10, price: 9 }, 
   { id: 5,  name: 'Product Five',  category: 20, price: 1 }, 
   { id: 6,  name: 'Product Six',   category: 20, price: 9 }, 
   { id: 7,  name: 'Product Seven', category: 30, price: 21 }, 
   { id: 8,  name: 'Product Eight', category: 20, price: 16 }, 
   { id: 9,  name: 'Product Nine',  category: 40, price: 6 }, 
   { id: 10, name: 'Product Ten',   category: 40, price: 5 }
];

Vrem ca, pornind de la datele de mai sus, sa obtinem un tabel de forma:

<table>

    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>category</th>
            <th>price</th>
        </tr>
    </thead>

    <tbody>
        <tr>
            <td>1</td>
            <td>Product One</td>
            <td>10</td>
            <td>7</td>
        </tr>
        <tr>
            ...
        </tr>
        ...
    </tbody>
</table>

Tabelul are doua sectiuni mari: thead si tbody. Nu folosim tfooter.

Fiecare dintre aceste sectiuni contin randuri (tr). Pentru zona thead avem header in interiorul randului (th) iar pentru continut (tbody) avem date (td).

Acum ca am stabilit structura, mergem mai departe cu analiza API-ului pus la dispozitie de JavaScript.


HTMLTableElement API

JavaScript ne pune la dispozitie un API dedicat pentru crearea si manipularea tabelelor in DOM. Nu e singurul mod prin care putem sa cream un tabel in pagina HTML.

Am putea sa folosim metoda generica document.createElement() careia ii putem specifica ce nod vrem sa cream (thead, tr, etc.). Insa va fi mai usor daca folosim un API dedicat pentru tabel (o vom folosi totusi pentru a crea elementul table).

Sa vedem cateva metode ale interfetei HTMLTableElement:

  • HTMLTableElement.createTHead() - returneaza elementul thead gasit care este copil al tabelului iar daca nu il gaseste il creaza.

  • HTMLTableElement.deleteTHead() - sterge thead-ul copil al elementului (table)

  • HTMLTableElement.insertRow() - returneaza un HTMLTableRowElement (randul nou din tabel). Daca este nevoie creaza si tbody. Ii putem specifica un index iar noul rand va fi introdus pe pozitia imediat anterioara acestui index.

Nu este nevoie sa utilizam append asa cum ar fi fost nevoie cu metoda document.createElement(). Deasemenea multe linii de cod se economisesc prin crearea automata a lui tbody sau thead.

  • HTMLTableElement.deleteRow() - sterge un rand din colectie. Va sterge randul de la indexul specificat. Putem trimite ca index valoarea -1 pentru a sterge ultimul rand.

HTMLTableRowElement API

Am vazut mai sus ca prin utilizarea metodei insertRow() se creaza un element de tipul HTMLTableRowElement .

HTMLTableRowElement -> HTMLElement -> Element -> Node

Sa vedem cele doua metode specifice puse la dispozitie pe un element de acest tip:

  • HTMLTableRowElement.deleteCell() - elimina o celula de la pozitia indexului trimis ca parametru.

  • HTMLTableRowElement.insertCell() - adauga o celula chiar inainte de pozitia specificata ca parametru. Daca nu trimitem parametru (sau trimitem -1) atunci se adauga la finalul randului.

Atat HTMLTableElement cat si HTMLTableRowElement au cateva proprietati ce ne pot fi utile, de exemplu:

HTMLTableElement.tHead, HTMLTableElement.rows (read-only), HTMLTableElement.tBodies (read-only)

Da, pot exista mai multe body-uri intr-un tabel.

HTMLTableRowElement.rowIndex, HTMLTableRowElement.cells ambele sunt read-only


Sa facem _setup_-ul pentru noul proiect

Fisierul HTML:

<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic HTML Table</title>
</head>
<body>
    <main></main>
    <script src="app.js"></script>
</body>
</html>

Codul JS il vom scrie in fisierul app.js. Pentru inceput cream dinamic un nod <table>

// app.js

let products = [
    { id: 1,  name: 'Product One',   category: 10, price: 7 }, 
    ...
 ];

window.onload = function(){

    let body = document.querySelector('main');
    let table = document.createElement('table');
    
    body.appendChild(table);
     
} 

Daca inspectam acum pagina HTML vom gasi un nou element <table> adaugat in interiorul lui <main>:


<main>
    <table></table>
</main>


table-utils.js

Vom crea o mini librarie reutilizabila pentru crearea dinamica a tabelelor.

// table-utils.js

const TUtils = (function(){     // IIFE - vom avea un singur obiect global cu toate metodele

    function createTHead(table, headData) {
        debugger
        let thead = table.createTHead();
        let hrow = thead.insertRow();   // fara sa fie nevoie de appendChild

        for (let prop in headData) {    // pentru fiecare proprietate a obiectului 
            let th = document.createElement("th");
            let text = document.createTextNode(prop);  // head-ul coloanelor
            th.appendChild(text);   // aici este gata elementul th

            hrow.appendChild(th);   // <tr> <th>id</th> </tr>
        }
    }

    return {
        createTHead
    }

}())


Nu uitam sa o incarcam in pagina index.html:

<!-- index.html -->

...
<body>
    <main></main>
    <script src="table-utils.js"></script>
    <script src="app.js"></script>
</body>
</html>

Utilizam prima varianta a mini librariei:

// app.js

...
window.onload = function(){

    ...
    body.appendChild(table);

    // cream header-ul pentru tabela
    TUtils.createTHead(table, products[0]);  
    
} 

  • am trimis primul obiect din array ca parametru, libraria va utiliza denumirile proprietatilor pentru a genera thead-ul tabelului.

Verificam rezultatul in browser. Ar trebui sa vedem tabelul fara date dar cu header. Codul HTML generat este:



<table>
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>category</th>
            <th>price</th>
        </tr>
    </thead>
</table>


Acum sa adaugam datele in tabel:

// table-utils.js

const TUtils = (function(){     // IIFE - vom avea un singur obiect global cu toate metodele

    ...
    function addTRows(table, tableData) {
        for (let item of tableData) {
            let drow = table.insertRow();   

            // iteram asupra proprietatilor din fiecare obiect pentru a crea coloanele (cells)
            for (prop in item) {
                let dcell = drow.insertCell();
                let text = document.createTextNode(item[prop]);

                dcell.appendChild(text); // <td>Product One</td>
            }

            // acum randul este gata
            // <tr> <td>1</td> <td>Product One</td> <td>10</td> <td>7</td> </tr>
        }

    }

    return {
        createTHead,
        addTRows
    }

}())

Acum sa trimitem datele pentru crearea randurilor in tabel:

// app.js

window.onload = function(){

    ...

    // incepem prin a adauga randurile de date pentru a crea `tbody`ea
    // daca rulam crearea thead-ului prima data, toate datele se adauga la thd
    TUtils.addTRows(table, products);

    TUtils.createTHead(table, products[0]);
    
} 


Aici conteaza ordinea in care rulam cele doua functii addTRows si createTHead. Asa cum este acum, totul este in regula, avem cele doua sectiuni thead si tbody, fiecare cu randurile specifice fiecarei sectiuni.


<table>
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>category</th>
            <th>price</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Product One</td>
            <td>10</td>
            <td>7</td>
        </tr>
        ...
    </tbody>
</table>

Daca am modifica ordinea de executie a celor doua functii

// app.js
    ...
    TUtils.createTHead(table, products[0]);
    TUtils.addTRows(table, products);
    ...

atunci HTML-ul generat ar arata ca mai jos:


<table>
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>category</th>
            <th>price</th>
        </tr>
        <tr>
            <td>1</td>
            <td>Product One</td>
            <td>10</td>
            <td>7</td>
        </tr>
        ...
    </thead>
</table>

Lipseste tbody. O solutie ar fi sa cream manual nodul tbody, sa-i luam referinta sa folosim insertCell de pe tbody:

    let drow = table.querySelector('tbody').insertRow();

Insa este mai usor sa punem intai datele, pentru ca in acest fel tbody va fi creat automat pentru noi.

De ce nu functioneaza corect acum? JavaScript gaseste deja elementul row din thead cand incepe sa adauge randurile de date si va folosi parintele randului existent (randul din thead) ca sa adauge celelalte randuri. Considera ca nu este nevoie sa creeze sectiune noua pentru tbody.

Nu avem o metoda createTBody ca sa obtinem o referinta la care sa adaugam randurile de date, in schimb avem metoda createTHead si atunci vom simplifica codul nostru daca prima data adaugam datele (primim gratis un tbody).


Sumar

Am creat dinamic un tabel in pagina folosind Web API. Am vazut ca putem simplifica foarte mult codul folosind API-ul dedicat pentru tabele: HTMLTableElement si HTMLTableRowElement

Am minimizat utilizarea metodei generice de creare a elementelor HTML document.createElement() si in felul asta si codul este mai simplu.

Libraria table-utils.js poate fi imbunatatita si poate fi chiar extinsa cu implementarea interactivitatii cu utilizatorul: sortare, filtrare, etc.

comments powered by Disqus