Search code examples
javascripthtmljsondom-manipulationdynamic-tables

Difficulty Building Dynamic Table from JSON Data Using JavaScript


I'm facing challenges in creating a dynamic table from JSON data using JavaScript. I've attempted several approaches, but none seem to be working as expected. Essentially, I'm trying to populate a table dynamically based on the data provided in JSON format. Despite my efforts, I'm unable to achieve the desired outcome.

What I've Tried:

I attempted to iterate through the JSON data using JavaScript and dynamically create table rows and cells accordingly. I explored various JavaScript libraries and frameworks like jQuery to simplify the process, but still encountered difficulties. I experimented with different methods for parsing the JSON data and injecting it into the table structure, but none produced the desired result.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Table</title>
    <script>
        const dynamicData = [
            { name: "GSW", years: [2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028], wins: [10, 11, 12, 13, 13, 13, 13, 14] },
            { name: "LAL", years: [2029, 2030, 2031, 2032, 2033, 2034, 2035], wins: [15, 16, 17, 18, 9, 10, 11] },
            { name: "PHX", years: [2000, 2001], wins: [1, 4] }
        ];

        function populateTable() {
            const tableBody = document.getElementById("data-body");

            dynamicData.forEach(team => {
                let yearChunks = chunkArray(team.years, 6);

                yearChunks.forEach((chunk, index) => {
                    if (index === 0) {
                        const row1 = document.createElement("tr");
                        const nameCell1 = document.createElement("td");
                        nameCell1.textContent = "name";
                        row1.appendChild(nameCell1);
                        const nameCell2 = document.createElement("td");
                        nameCell2.setAttribute("colspan", `${chunk.length + 1}`);
                        nameCell2.textContent = team.name;
                        row1.appendChild(nameCell2);
                        tableBody.appendChild(row1);
                    }

                    const row2 = document.createElement("tr");
                    if (index === 0) {
                        const titleCell = document.createElement("td");
                        titleCell.textContent = "year";
                        row2.appendChild(titleCell);
                    }
                    chunk.forEach(year => {
                        const yearCell = document.createElement("td");
                        yearCell.textContent = year;
                        row2.appendChild(yearCell);
                    });
                    tableBody.appendChild(row2);

                    const row3 = document.createElement("tr");
                    if (index === 0) {
                        const winsCell = document.createElement("td");
                        winsCell.textContent = "wins";
                        row3.appendChild(winsCell);
                    }
                    chunk.forEach((_, i) => {
                        const winsCell = document.createElement("td");
                        winsCell.textContent = team.wins[index * 6 + i];
                        row3.appendChild(winsCell);
                    });
                    tableBody.appendChild(row3);
                });
            });
        }

        function chunkArray(array, size) {
            const chunks = [];
            for (let i = 0; i < array.length; i += size) {
                chunks.push(array.slice(i, i + size));
            }
            return chunks;
        }

        window.onload = populateTable;
    </script>

</head>

<body>
    <table border="1">
        <tbody id="data-body">
        </tbody>
    </table>
</body>

</html>

What my expected result:

enter image description here

What I've got:

enter image description here

However, I'm struggling to achieve this. I have team data and want to generate a table like this using the provided data.

    {
        "name": "GSW",
        "year": [
            "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030",
            "2031", "2032", "2033", "2034", "2035"
        ],
        "wins": [
            10, 11, 12, 13, 13, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21
        ],
    },
    {
        "name": "LAL",
        "year": [
            "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030",
            "2031", "2032", "2033", "2034", "2035"
        ],
        "wins": [
            9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
        ],
    },
    {
        "name": "PHX",
        "year": ["2000", "2001"],
        "wins": [1, 4]
    }
]

Technically, I want to create a table with a maximum of 6 columns. When it reaches 6 columns, it should wrap, starting from the next row of the table. If the data for one team is not enough to fill the table, it should take data from the next team. The last row can be an empty row.


Solution

  • The hardest part here is to figure out the correct colspan for the cells in the name rows.

    I decided to first of all put all the data into "continous" arrays (log restructuredData to console to see what I mean), so that it can be looped over all in one go, to create the table rows and cells.

    I am setting a name into every cell of the name rows first here - and then clean them up (remove cells with repeated names, fix colspan) later, after the table has been inserted into the DOM.

    const dynamicData = [{
        "name": "GSW",
        "years": [
          "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030",
          "2031", "2032", "2033", "2034", "2035"
        ],
        "wins": [
          10, 11, 12, 13, 13, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21
        ],
      },
      {
        "name": "LAL",
        "years": [
          "2021", "2022", "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030",
          "2031", "2032", "2033", "2034", "2035"
        ],
        "wins": [
          9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
        ],
      },
      {
        "name": "PHX",
        "years": ["2000", "2001"],
        "wins": [1, 4]
      }
    ];
    
    let cols = 6;
    
    let restructuredData = {
      names: [],
      years: [],
      wins: [],
    };
    
    dynamicData.forEach(function(e) {
      let names = [];
      names.length = e.years.length;
      restructuredData.names = restructuredData.names.concat(names.fill(e.name, 0, names.length));
      restructuredData.years = restructuredData.years.concat(e.years);
      restructuredData.wins = restructuredData.wins.concat(e.wins);
    });
    
    //console.log(restructuredData);
    
    let rows = Math.ceil(restructuredData.years.length / 6);
    let table = '<table>';
    for (let r = 0; r < rows; ++r) {
      table += '<tr class="names"><td>name</td>';
      for (let c = 0; c < cols; ++c) {
        table += '<td>' + (restructuredData.names[r * cols + c] || '') + '</td>';
      }
      table += '</tr>';
      table += '<tr><td>year</td>';
      for (let c = 0; c < cols; ++c) {
        table += '<td>' + (restructuredData.years[r * cols + c] || '') + '</td>';
      }
      table += '</tr>';
      table += '<tr><td>wins</td>';
      for (let c = 0; c < cols; ++c) {
        table += '<td>' + (restructuredData.wins[r * cols + c] || '') + '</td>';
      }
      table += '</tr>';
    }
    table += '</table>';
    
    let tableContainer = document.querySelector('#tableContainer');
    tableContainer.innerHTML = table;
    
    // clean up the name row cells & set appropriate colspan
    Array.from(tableContainer.querySelectorAll('tr.names')).forEach(function(row) {
      let cells = row.cells;
      let cellIndex = 1;
      let colSpan = 1;
      while (cells[cellIndex]) {
        if (cells[cellIndex].textContent && cells[cellIndex + 1] && cells[cellIndex].textContent === cells[cellIndex + 1].textContent) {
          cells[cellIndex].colSpan = ++colSpan;
          cells[cellIndex + 1].remove();
        } else {
          colSpan = 1;
          ++cellIndex;
        }
      }
    });
    td {
      border: 1px solid;
    }
    <div id="tableContainer"></div>