0

I have hit a scaling problem with an HTML table, whilst manipulating it in JavaScript.

I have a JavaScript code blob that dynamically creates a table from a JSON array. It fixes the header and allows the table body to scroll, and it works well.

The header is a separate table, and I strongly control cell widths to ensure that the header table's cells match up with the body table cells.

The body table cells all have their width explicitly set.
I iterate the rows in the table, and then interate the cells within that loop, and set each cells width.

This iteration is not scaling, as the rows grow to 100 and the columns get to 40 odd, I get complaints from Firefox that the script is hanging or unresponsive. It is unresponsive, because it is busy setting all the cell widths.

nz.dynatable.setTableColumnWidths = function (table, arrayColumnWidths) {
    // Iterate the table and set width settings for each cell
    for (var i = 0; i < table.rows.length; ++i) {
        for (var j = 0; j < table.rows[i].cells.length; ++j) {
            var width = arrayColumnWidths[j] || 0;
            table.rows[i].cells[j].style.width = width.toString() + "px";
        }
    }
}

Q: Is it possible to to set cell widths for a table in one row and have all the other cells in the table fall into line with this? Is Firefox getting whacked because changing the cell widths is causing it to recalc the table size on each loop?

The full code chunk is in a public GitHub repo: https://github.com/Hiblet/DynaTable

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Steve Hibbert
  • 2,045
  • 4
  • 30
  • 49
  • 3
    Did you try just doing the first row? That's normal table behavior. – isherwood Sep 24 '14 at 17:21
  • Yes that's the old school trick, just set your widths on the first row -- and pray there are no colspans involved :P Anyone use colgroup anymore? – wwwmarty Sep 24 '14 at 18:06
  • No colspans, tis all under my control. Will give the first row a go, if that works it will speed up that part massively. Will try tomorrow, thanks for replies. – Steve Hibbert Sep 24 '14 at 22:52
  • Sorry for the delay. First row is enough, so that's great. Would @isherwood supply a token answer so I can mark it as such? Thanks very much for the assistance to both of you. – Steve Hibbert Sep 25 '14 at 13:08
  • 1
    As a note: When I sorted the rows and re-ordered, the top-most row ceases to be first in the table, formatting stuffs up; I had to store column widths and reset first row after a sort. GitHub updated. All Good! – Steve Hibbert Sep 25 '14 at 14:50

2 Answers2

0

Try this: I had have same problem and this is help me:

let tables = document.getElementsByClassName("table");

if(tables.length == 2){
    let firstTableCol = tables[0].querySelectorAll('colgroup col');
    let secondTableCol = tables[1].querySelectorAll('tr')[0].querySelectorAll('td');
    if(firstTableCol.length == secondTableCol.length){
        for(let index = 0; index < firstTableCol.length; index++){
            firstTableCol[index].style.width = secondTableCol[index].offsetWidth + "px";
        }
    }else
        console.log('cols count mismatch');
}else
    console.log('count of tables are bigger than 2');
thehennyy
  • 4,020
  • 1
  • 22
  • 31
  • I worked around the problem by caching the current column widths and rolling them over to the rebuilt table. I think I will re-write my internal table update code to just update the data, rather than the table-and-data, as building the HTML seems to be the problem. – Steve Hibbert Aug 10 '17 at 15:19
0

I tried a few ways to skin this cat and here were my findings. The final solution ended up being very clean and performant, with the caveats that I tested on Chrome, and not other browsers, and with moderately large, but not huge, tables. To summarize, use css such as to following to set the 5th column to width 70, and use javascript to modify that css:

table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}

More details:

  • you need to set both min-width and max-width to avoid columns that you are NOT trying to resize from doing funky stuff in order to keep the table size constant

  • you can do this using element.style.xxxWidth, but a simple approach such as in the OP doesn't scale well, as the OP notes.

    • One way to deal with this is to only update the elements that are in a viewable area (let's call this the "viewable area approach"). You can do this by comparing the parent.getBoundingClientRect() results from the parent object and the object you are checking (see e.g. How to tell if a DOM element is visible in the current viewport?)
    • I was able to get this to work pretty well as long as calls to getBoundingClientRect() (which are expensive) were kept to a minimum (i.e. do it once per column, not per cell). Because I didn't want to maintain a cache of what cells were updated and what were not, after updating viewable cells, I would then update the non-viewable cells using an async function call. This way the UI appeared much more responsive even though it was still doing stuff in the background
  • However, anything involving direct changes to element.style.xxxWidth felt messy, especially after having to add the code to only update viewable elements. Furthermore, though performance was much better with that change, it still seemed possibly sub-optimal. So the final solution I ended up using was to do the following (let's call this the "dynamic stylesheet approach"):

    • assume each table will have a unique element ID
    • initialize the table by creating a new style (document.createElement('style')) for each column. The style that contain one rule that will only select that column for the table with that ID, e.g. for table with id "my_table", column 5 (cellIndex 4), to set width to 70:

      table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}
      
    • add the newly created styleElements to the table element (not the document), so that if the table is removed from the DOM, these extra styles go away also

    • to change a column width, the only thing that needs to change is the maxWidth / minWidth pieces of rule 0 of the related styleElement (note, you could also do this with a single stylesheet that has multiple rules, but I found it easier to use a separate stylesheet per column with one rule each).

Overall, using the dynamic stylesheet approach was the winner in my view for the following reasons:

  • it performs well with tables that are large enough to have performance issues under the "naive" approach described in the OP (though I haven't compared the performance of other approaches on very large tables)
  • it is clean: no element.style changes are required, and it doesn't clutter your basic document DOM as the relevant style elements are attached to the related table (need to test cross-browser)
  • if its performance isn't already optimized automatically by browsers, its design should make optimization straightforward by leaving the performance challenge up to the browser to handle
mwag
  • 3,557
  • 31
  • 38