How to set HTML Table column widths explicitly in JavaScript - javascript

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

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');

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

Related

Is it possible to generate callout/indicator ("continues...") when table breaks across column or page?

We are designing HTML pages for print and one of the requirements for tables is, whenever a page or column break occurs, to generate an indicator like "Continues..." below the table before the break (page or column) and in the header of the continuation of the table, another indication, like table title and the text "Continued", followed by the normal flow of the content.
For the top-level container, we are planning to use CSS multi-columns and normal html table element for the table. It seems CSS columns do not expose any pseudo-classes to decorate the column breaks.
Below is the layout I'm looking for,
Here's how I'd approach it:
Put hidden div above table
<div id="cont-label" style="display:none;">Continues...</div>
Know table size
By either sending its size to UI, or getting its size usign javascript:
var size= $('#mytable tr').length;
size is saved in javascript or in table div data attribute data-size=12
Know if table is "chopped"
If table on home is displaying a number of rows that's less than table size, either by counting rows or by having fixed number of rows displayed (depends on your design). Let's say displayed size is displayedSize.
Compare and act
Better to be in jQuery
Get both size and displayedSize and compare:
if( displayedSize < size ){
$("#cont-label").show();
}
I will assume that the next page will always show the text "Continued" because otherwise why have full page for table in the first place. I see you display the whole table and not the chopped part only ad this makes implementation easier, please correct me.

How can I compensate for longer load times when dynamically setting div dimensions with CSS and JS?

I am creating a Polymer app which displays information in rows in a custom element "table". I am using polymer flexbox classes to initially position elements. I need to take measurements with JS after the elements have loaded and assign widths to elements in the table accordingly. The problem goes something like this:
row 1
*--------------------*
| *-----------*|
| | el a ||
| *-----------*|
*--------------------*
row 2
*--------------------*
| *------*|
| | el b ||
| *------*|
*--------------------*
Row 1 and row 2 have fixed widths, but el a or bcould be of any width. Regardless of contents of el a or b, I want the el's contained in the rows to be all the same width as the widest. So in this case, a is widest so el b should adjust its width to match a.
I am using polymers attached method to ensure the elements are loaded before taking scrollWidth measurements via JS. I initially ran into the problem of getting undefined elements when I tried to grab them with querySelector, but I fixed this using Polymer's async. I can style the elements fine in a modern browser just fine. Consider the following code.
attached: {
this.async(function() {
console.log(Polymer.dom(this.root).querySelector('#a').scrollWidth);
console.log(Polymer.dom(this.root).querySelector('#b').scrollWidth);
}, 100);
}
A modern version of chrome will return something like 100px for a and 60px for b. If I test an older version of chrome or a different browser like firefox, a and b will be 5-15px less than what modern chrome measured. I found out that if I increase the async time enough, no matter the age of the browser, I will eventually get a measurement matching what modern chrome returned. This is to say while it appears the div exists on the page and I can grab it with querySelector, it seems to be fluctuating so that it is not at its full width yet.
I don't like guessing how long it will take for the elements to fully load before I can take measurements. I am trying to figure out a way I can be 100% confident that the elements are done loading or perhaps an alternate method of styling these elements I have overlooked. I have had the most success with using setTimeout on the function that is doing the measuring and waiting for it to measure the same thing several times in a row. But even that has been inconsistent at times and occasionally b will be slightly less wide than a when the web page appears.
One option when you need to keep checking things, even if it's been true a couple times, is to continue to use setTimeout infinitely until the values are no longer changing for a certian # of iterations. For example, if they are still the same after 10 timeouts, stop the timeouts from going all together.
something like...
var count = 0;
function check() {
do_something();
if (same_as_last_time == true) {count++}
if (count < 10) setTimeout (check,100);
}
setTimeout (check,100);
Although this could be wildly inefficient and possibly ineffective depending on how staggered your data is appearing.
I believe the actual solution here lies in the fact that you are using the variable ".scrollWidth" as your measurement point, which is a terrible measurement to standardize your widths across browsers. See this SO post for more info on that (https://stackoverflow.com/a/33672058/3479741)
I would recommend instead using a different method of acquiring the width and compare to see whether your results are more consistent. There are many options that remain more consistent than ".scrollWidth" in the article above.

How to limit a <td> to only three lines?

I would like to achieve a unified look and feel across all my table rows.
When you look at my example below you can see that the note in the middle goes over 4 lines and thats not so pretty.
I was hoping to limit all <td> to 3 lines.
If there is more to be shown than three lines, then it should cut the content with ... [click for more] and put the content inside a collapseable element, so that when clicked on it it would show the whole content.
The latter shouldn't be a problem, but how do I limit the content to only three lines? Shall I count the characters to make the decision upon that? Is there a better strategy? I am using Django by the way,but I am happy to use javascript, jquery or any css magic instead to solve this.
Update:
The accepted answer is very good. However it comes with a caveat, which isn't easy to solve.
if you have a neighbouring td that already goes over three lines, while the current td is only two lines we will get an infinite while loop.
while($(this).innerHeight() / $(this).css('line-height').slice(0,-2) >= 3){ .. }
Since $(this).innerHeight() can't decrease because of the neighbouring cell holding the height up high. I think if it was possible to get the css of the current td and copy it across the content completely in a separate field, where neighbouring tds can't interfere, we would get the optimal solution.
Update 2:
As Assad mentioned, the solution is to put a div wrapper around the content of td and set the class on the div inside the td rather than on the td itself. It works flawlessly.
Assuming you are using jQuery, you could find all td elements that exceed a certain number of lines using:
$('td').filter(function(){
return $(this).innerHeight() / $(this).css('line-height').slice(0,-2) > 3; //more than 3 lines
});
You could then apply collapsible elements to these td elements.
Here is a demonstration (using paragraphs instead of tds): http://jsfiddle.net/jM4ZY/1/
Here is an example of cutting off content to fit 3 lines, then adding a more button: http://jsfiddle.net/jM4ZY/2/
As far as the edit is concerned, this is easily resolved by using an inner wrapper for your content; possibly a div element. You can then measure the height of this element, which is independent of the height of neighboring cells.
Another jQuery solution is described here
It is described how to change the Text by counting the number of letters in the displayed text. If you are unsure about the number of letters, or want to make it dependent of the text-length you can calculate it by using this snipped
$.fn.textWidth = function(){
var html_org = $(this).html();
var html_calc = '<span>' + html_org + '</span>';
$(this).html(html_calc);
var width = $(this).find('span:first').width();
$(this).html(html_org);
return width;
};
which I took from Calculating text width

Modifying the style attributes of selected table cells with jQuery?

I'm building a pretty basic HTML table creator/editor (based on a designMode iframe) at work, using direct DOM manipulation. It's a pain, obviously due to Internet Explorer.
When in designMode, a table inserted into the editing area iframe is resizable and the contents of the cells can be freely edited. In Firefox, rows and columns can also be added and removed. I'm currently focused on editing border widths, foreground and background colors and other things that require DOM work.
The trouble is the lack of proper DOM Selection/Range functionality in IE6/7. I'm unable to find the containing nodes for several simultaneously selected cells. For a single cell it's doable with parentElement, but for several selected cells, parentElement is the TR node that houses the TD cells. I can't figure out how to extract node references to only those TD cells inside that TR that have been selected, due to lack of anchorNode, focusNode and the various offsets that W3C DOM provides.
I've already got the table creation and the style modification for individual cells as well as groups of selected cells implemented for W3C compliant browsers, but I'm completely stuck with the IE implementation. Could jQuery help me? I've never used it, but it seems intuitive enough that it will take less time to master than it will to figure out how to do this with the IE DOM alone.
There are three basic style modification scenarios that need to work:
A table cell that has not been explicitly selected with Ctrl/Cmd-clicking, but has the text cursor inside it, must have its background color changed. The cell may have formatted text or other parentNode/childNode-relationship complicators in it.
Several explicitly selected table cells (Ctrl/Cmd-clicked, Shift-selected or just "painted over" with the mouse) must have their background colors changed. This has to work for contiguous rectangular selections as well as for scattered, individual selected cells.
Table-level modifications (border width, color etc.) for the "selected table" need to be possible. That is, in the case of several tables in the editing area, the modification will take place for one or more tables that either have cursor focus (scenario 1) or have selected cells in them (scenario 2).
In Firefox, I already have the code for all three scenarios working. Now I need a cross-browser solution. Can anybody help me?
(IE's problems with selections and ranges have been discussed here before, but not in the context of jQuery. I found these at a glance: 164147, 218043, 235411)
If I understand you properly, you want the general code for selecting table cells, and changing properties (CSS attributes) for the selection.
You can do this easily in jQuery.
var curTableCell = null; // "Softclicked" - not part of the selection (1)
// We call this in the click event below. You'd probably want this for keyboard events as well (for arrow key nav, etc.)
function softclick(element) {
$(curTableCell).removeClass('softclicked');
curTableCell = element;
$(element).addClass('softclicked');
}
$('td, th').click(function() {
if(keyHeld) { // Dunno how you do this (I'm not good at Javascript)
$(this).toggleClass('selected'); // Explicitly added/removed to/from selection (2)
} else {
softclick(this);
}
});
/* When you want to do something on selection: */
$('td.selected, th.selected').css({borderColor: 'red', borderWidth: '1px'});
/* When you want to do something on selected tables (3): */
$('td.selected, th.selected').parents('table')
.css({borderColor: 'red', borderWidth: '1px'});
$('td.selected, th.selected').parents('table').children('td') // Change things on all of table's cells
.css({borderColor: 'red', borderWidth: '1px'});
$('td.selected, th.selected, td.softclicked, th.softclicked').parents('table').children('td') // Change things on all of table's cells, including tables of "softclicked" cells
.css({borderColor: 'red', borderWidth: '1px'});
(I am not too good at Javascript or jQuery (am learning at the moment), but I hope this is enough to get you started.)

Copy half of an HTML child nodes array

I have an HTML element (let's say a division) that contains a set titles (h2) and paragraphs (p) with other element inside (some links for example, a).
My goal is to cut my HTML element in 2 same size element.
Constrains : paragraphs, titles, and block should not be cut
My idea asbout this was to browse inside the child nodes list and copy childNodes of each paragraphs.
Each time I copy a paragraph, i compute the size in ordre to know if I reached the half size of the division.
Here is some code to explain it:
var elm = document.getElementById('article_colonnes'); // I want to cut this into 2 parts
var paragraphesNumber = paragraphes.length;
var halfSize = elm.innerHTML.length / 2 ;
var col1 = document.getElementById('col1');
var col2 = document.getElementById('col2');
var i=0;
do {
var node = createNodeFromParagraphe(paragraphes[i]);
if(node) {
col1.appendChild(node);
// update the size of the 1st column by updating inner HTML
col1String = col1String + paragraphes[i].innerHTML;
}
i++;
// compute the size of the 1st column
col1Size = col1String.length;
}
while(col1Size < halfSize || i < paragraphesNumber) ;
And I do the same for the 2nd column.
Thanks for your help.
If your trying to make an automatic two column layout; take a look at multicolumnlists
To get a true two column layout, you need the height of the paragraphs (not the characters in it). The problem here: The only way to get the height is to wait for the browser to layout the page unless you specify the width of every element. Otherwise, the user would see an ugly flicker.
So I suggest to try this:
Use a multi column layout which most browser support. If the browser is not IE <= 7, you're done.
For IE 7 and lower, set a fixed width for all elements. Wait for the browser to layout the page (it should be enough to run this code in body.onload or from a script tag at the end of the page). Put all elements into a div with the id "col1".
Use JavaScript to find the element at half the height of the the div "col1". (see here)
If this element is a heading, skip one element.
Move the next element (and all beyond) into the div "col2".
That should give you a longer col1 and a shorter col2.
If moving doesn't work, insert an invisible character after you identified the split point, get col1.innerHtml, split it at the split char and use the resulting two strings as innerHtml for col1 and col2.
If that flickers a lot, try to hide col1 and col2 while your script runs (display='None').

Categories

Resources