2

I have a two-dimensional data grid where I have a header row in one table and multiple lines of data in a table directly underneath (they need to be in separate tables). I need the width of a header element to adjust itself to the width of the data element if the data element is wider, and the width of the data element to adjust itself to the width of the header element if the header element is wider. Problem is, it's not setting the widths correctly.

This routine is run after the table is loaded:

var headtbl = document.getElementById("HeadTbl");
var bodytbl = document.getElementById("BodyTbl");
alert(headtbl.rows[0].cells[0].offsetWidth + " AND " + bodytbl.rows[0].cells[0].offsetWidth); // THIS RETURNS "**8 AND 17**"
headtbl.rows[0].cells[0].style.width = "17px"; // HARD-CODED THIS FOR DEMO PURPOSES
alert(headtbl.rows[0].cells[0].offsetWidth); // THIS RETURNS **21**!!  And, obviously, the data grid is thrown off
  1. I am using a valid DOCTYPE (not quirks mode)
  2. No other stylesheets are coming into play
  3. No styles are being defined in the TABLE/TR/TD elements
  4. I also tried .style.setProperty and .setAttribute definitions, both with and without "important"
  5. Use of THEAD/TBODY/TH elements makes no difference

I've searched online and haven't found this particular issue addressed. Can someone assist, please?

EDIT: Here is the code distilled down to the absolute minimum required to demonstrate the issue:

function formatGrid() {
  var headtbl = document.getElementById("HeadTbl");
  var bodytbl = document.getElementById("BodyTbl");
  alert(headtbl.rows[0].cells[0].offsetWidth + " AND " + bodytbl.rows[0].cells[0].offsetWidth); // 49 and 84
  headtbl.rows[0].cells[0].width = "84px";
  alert(headtbl.rows[0].cells[0].offsetWidth); // 88!
}
<!DOCTYPE html>
<html>

<head>
</head>

<body onLoad="formatGrid()">
  <table id="HeadTbl" border="1">
    <tr>
      <td>Author</td>
    </tr>
  </table>
  <table id="BodyTbl" border="1">
    <tr>
      <td>Shakespeare</td>
    </tr>
  </table>
</body>

</html>

4
  • 3
    Why do you put the headers and data in different tables? Put them in the same table and they'll be aligned automatically. Commented Jun 26, 2024 at 21:58
  • Please post a minimal reproducible example. You can use a Stack Snippet to make it executable. Commented Jun 26, 2024 at 21:58
  • Hello Barmar. I have to put them in separate tables because the header needs to remain visible while the body can scroll both vertically and horizontally. I didn't include the <DIV>s in my sample code because I didn't want to make things too complicated. The main issue is that the <TD> widths are out of sync. That's what I need to resolve. Commented Jun 26, 2024 at 23:10
  • Refer to the edit I made to my original post Commented Jun 26, 2024 at 23:29

3 Answers 3

2

There is an important thing here going on, which I was curious when seeing your own answer as why applying the style to a div and not to the td made a difference.

Playing around I saw that overflow was not working correctly on td, then I searched some questions about it like this one: Why does overflow:hidden not work in a td?.

One solution could be to change the CSS display of the td to block: display: block;

So I made some tables with:

  • TD width | TD overflow hidden | TD display block
  • TD width | TD display block
  • DIV width | DIV overflow hidden | TD display block
  • DIV width | TD display block

Here is a visual comparation of with and without display block:

enter image description here

const printWidth = () => {
   var a = document.getElementById("a"), aa = document.getElementById("aa");
   var b = document.getElementById("b"), bb = document.getElementById("bb");
   var c = document.getElementById("c"), cc = document.getElementById("cc");
   var d = document.getElementById("d"), dd = document.getElementById("dd");
   
   console.log(a.rows[0].cells[0].offsetWidth, aa.rows[0].cells[0].offsetWidth);
   console.log(b.rows[0].cells[0].offsetWidth, bb.rows[0].cells[0].offsetWidth);
   console.log(c.rows[0].cells[0].offsetWidth, cc.rows[0].cells[0].offsetWidth);
   console.log(d.rows[0].cells[0].offsetWidth, dd.rows[0].cells[0].offsetWidth);
};


document.addEventListener('DOMContentLoaded', printWidth);
<table id="a" border="1">
   <tr>
  <td style="width:30px; overflow:hidden; display:block;">Author</td>
   </tr>
</table>
<table id="aa" border="1">
   <tr>
  <td style="width:30px; overflow:hidden; display:block;">Shakespeare</td>
   </tr>
</table>
<br />
<table id="b" border="1">
   <tr>
  <td style="width:30px; display:block;">Author</td>
   </tr>
</table>
<table id="bb" border="1">
   <tr>
  <td style="width:30px; display:block;">Shakespeare</td>
   </tr>
</table>
<br />
<table id="c" border="1">
   <tr>
  <td style="display:block;">
     <div style="width:30px; overflow:hidden;">Author</div>
  </td>
   </tr>
</table>
<table id="cc" border="1">
   <tr>
  <td style="display:block;">
     <div style="width:30px; overflow:hidden;">Shakespeare</div>
  </td>
   </tr>
</table>
<br />
<table id="d" border="1">
   <tr>
  <td style="display:block;">
     <div style="width:30px;">Author</div>
  </td>
   </tr>
</table>
<table id="dd" border="1">
   <tr>
  <td style="display:block;">
     <div style="width:30px;">Shakespeare</div>
  </td>
   </tr>
</table>

Sign up to request clarification or add additional context in comments.

1 Comment

also, your solution using DIV witdh already behaves like what you want (adding display block doesn't change anything so you should not do both).
1

You can create a function to iterate through the columns, and calculate the minimum width of the two columns that finding the max.

const measureWidth = (el) => el.getBoundingClientRect().width;

const fitColumns = (frozenTable) => {
  const bcells = frozenTable
    .querySelectorAll('.frozen-table-body tbody tr:first-child td');

  frozenTable
    .querySelectorAll('.frozen-table-header thead tr th')
    .forEach((hcell, index) => {
      const bcell = bcells[index];
      if (bcell) {
        const width = Math.ceil(Math.max(...[hcell, bcell].map(measureWidth)));
        hcell.style.width = `${width}px`;
        bcell.style.width = `${width}px`;
      }
    });
  };

const formatGrid = () => {
  fitColumns(document.querySelector('.frozen-table'));
};

document.addEventListener('DOMContentLoaded', formatGrid);
window.addEventListener('resize', formatGrid);
<div class="frozen-table">
  <table class="frozen-table-header" border="1">
    <thead>
      <tr>
        <th>Author</th>
        <th>Title</th>
        <th>Year of Publication</th>
      </tr>
    </thead>
  </table>
  <table class="frozen-table-body" border="1">
    <tbody>
      <tr>
        <td>Shakespeare</td>
        <td>Romeo &amp; Juliet</td>
        <td>1597</td>
      </tr>
    </tbody>
  </table>
</div>

6 Comments

Thanks, Mr. Polywhirl! This works great. Do you happen to know why such a complicated process is needed? Is there some inherent flaw in the DOM that makes setting the width property of a table cell inaccurate?
@SteadyEddie you just need to set each column header and every body call in the first row to the same width. Since these elements are detached, they do not know anything about one another.
Yes, I understand. And that is what I was attempting to do. I just don't understand why four pixels were being added (and sometimes more than four) when I set the width of the header element to match the offsetWidth of the body element.
"I just don't understand why four pixels were being added" - quote MDN, "Typically, offsetWidth is a measurement in pixels of the element's CSS width, including any borders, padding, and vertical scrollbars (if rendered)." - your table cells have 1px padding and 1px border on either side, so that's your 4px. You assign that offset width as the width for the header cells - and therefor end up with a cell that is four pixels wider. You can of course use the box-sizing properties to "fix" that ...
Thanks, CBroe. I see what you're saying about offsetWidth. When I use clientWidth it still increases the value, but by two instead of four. Nothing is ever easy! Thanks for introducing me to box-sizing.
|
0

Thanks to all who responded. I discovered the only way to make a td an exact width, regardless of its contents, is to insert a div.

In the header:

<td><div style="width:150px">Author</div></td>

In the body:

<td><div style="width:150px">William Shakespeare</div></td>

Apparently, td style="width:xxxpx" is not reliable and can change based on the contents.

1 Comment

I found interesting why applying styles directly to TD or to the DIV made a difference at all, so I went playing around and I think I found out, maybe it is because the default CSS display of TD.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.