6
\$\begingroup\$

Here is my solution for a fixed table header with scrollable body and aligning columns.

The requirements I wanted to achieve were:

  • Fix <thead> while <tbody> can scroll while the all the <th> and <td> cells of one column all have the same width
  • No fixed width
  • Pure HTML, CSS, JS
  • Bonus points for pure CSS solution

What could still be improved:

  • Make the scroll bar as longer so there is no free area in the top right
  • Improve the Javascript, I'm sure it can be rewrote to be more efficient
  • Clean the code, especially the Javascript
  • Pure CSS solution, if possible

I would also like to have general advice on my coding style, good or bad practices and maybe an evaluation of how efficient this code is. How good would it perform if the matrix size grows? Right now, I'm just happy that my code works in my case. :)

You can find the matrix that needs to be tweaked here: JsFiddle

The table in this example is from a match plan for last year for my sports team.

// jshint esversion: 6
// jshint browser: true
// jshint devel: true

const thElements = document.getElementsByTagName("th");
const tdElements = document.getElementsByTagName("td");
let width = [];
let tempResult = 0;
// calculate needed width
for (let i = 0; i < thElements.length; i++) {

  //compare actual width
  if (thElements[i].offsetWidth > tdElements[i].offsetWidth) {

    // get inner width because thats what we will set
    tempResult = window.getComputedStyle(thElements[i], null)
      .getPropertyValue("width");
    width[i] = `${tempResult.toString()}`;
  } else {

    // get inner width because thats what we will set
    tempResult = window.getComputedStyle(tdElements[i], null)
      .getPropertyValue("width");
    width[i] = `${tempResult.toString()}`;
  }
}

// set column width
for (let i = 0; i < thElements.length; i++) {
  thElements[i].style.width = width[i];
  tdElements[i].style.width = width[i];
}
/*container */

#club_plan {
clear: both;
overflow-x: auto;
position: absolute;
top: 50%;
left: 50%;
margin-right: -50%;
transform: translate(-50%, -50%)
}

thead tr {
position: relative;
}

tbody {
display: block;
max-height: 150px;
overflow-x: auto;
width: 100%
}

thead {
display: inline-block;
width: 100%;
}


/* decoration*/

table {
font: 12px Verdana;
border-collapse: collapse;
border-spacing:1px
}

th {
padding: 5px;
border: 1px solid #888888;
}
td {
padding: 5px;
border-style: solid;
border-width: 0px 1px 1px 1px;
border-color: #888888;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>testcode</title>
  <link href="style.css" rel="stylesheet" type="text/css">
</head>

<body>
  <div id="club_plan" style="display: block; overflow-x: auto;">
    <table border="0" cellpadding="0" cellspacing="0" class="result-set" id="club_plan_table">
      <thead>
        <tr>
          <th>Tag</th>
          <th>Datum</th>
          <th>Uhrzeit</th>
          <th>Liga</th>
          <th>Heimmannschaft</th>
          <th>Gastmannschaft</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Do.</td>
          <td>Heute</td>
          <td>19:45</td>
          <td>H1KK</td>
          <td class=" teams">TuS Hiltrup V</td>
          <td class=" teams">DJK Borussia Münster IV</td>
        </tr>
        <tr>
          <td>Fr.</td>
          <td>30.09.2016</td>
          <td>19:30</td>
          <td>H3KK</td>
          <td class=" teams">SC Westfalia Kinderhaus V</td>
          <td class=" teams ">DJK Borussia Münster VI</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>20:00 v</td>
          <td>HBL</td>
          <td class=" teams ">DJK Borussia Münster II</td>
          <td class=" teams">1. TTC Münster III</td>
        </tr>
        <tr>
          <td>Sa.</td>
          <td>01.10.2016</td>
          <td>18:30</td>
          <td>HNRWL</td>
          <td class=" teams ">DJK Borussia Münster</td>
          <td class=" teams">1. TTC Münster</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>18:30</td>
          <td>DNRWL</td>
          <td class=" teams ">DJK Borussia Münster</td>
          <td class=" teams">TTC Werne 98</td>
        </tr>
        <tr>
          <td>Di.</td>
          <td>04.10.2016</td>
          <td>20:15</td>
          <td>H1KK</td>
          <td class=" teams ">DJK Borussia Münster IV</td>
          <td class=" teams">1. TTC Münster VII</td>
        </tr>
        <tr>
          <td>Do.</td>
          <td>06.10.2016</td>
          <td>20:00</td>
          <td>H2KK</td>
          <td class=" teams">1. FC Gievenbeck IV</td>
          <td class=" teams ">DJK Borussia Münster V</td>
        </tr>
        <tr>
          <td>Fr.</td>
          <td>07.10.2016</td>
          <td>19:30</td>
          <td>HKL</td>
          <td class=" teams">DJK SC Nienberge</td>
          <td class=" teams ">DJK Borussia Münster III</td>
        </tr>
        <tr>
          <td>Sa.</td>
          <td>08.10.2016</td>
          <td>17:30 v</td>
          <td>HBL</td>
          <td class=" teams">SC Westfalia Kinderhaus II</td>
          <td class=" teams ">DJK Borussia Münster II</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>18:30</td>
          <td>DNRWL</td>
          <td class=" teams">TSSV Bottrop</td>
          <td class=" teams ">DJK Borussia Münster</td>
        </tr>
        <tr>
          <td>So.</td>
          <td>09.10.2016</td>
          <td>10:00</td>
          <td>HNRWL</td>
          <td class=" teams">GSV Fröndenberg</td>
          <td class=" teams ">DJK Borussia Münster</td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
<script src="script.js"></script>

</html>

\$\endgroup\$
6
\$\begingroup\$

Pure HTML and CSS

Solution below may be a little hackery, because CSS wasn't made for things like that, yet. It has it's downs when you look deeper, but well: that's what you get for writing hackery CSS. Also, it resembles design from your question from before the edits you made.

.table-container {
  border: 1px solid #963;
  display: inline-block;
  height: 150px;
  padding-top: 1.875em;
  position: relative;
}
.table-container > div {
  border-color: #888;
  border-style: solid;
  border-width: 1px 1px 2px 1px;
  height: 1.7em;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}
table {
  display: block;
  height: 150px;
  overflow-y: auto;
}
th {
  padding: 0 5px;
  text-align: center;
}
th:first-child > div {
  border-left: none;
  padding-left: 7px;
}
th > div {
  border-left: 2px solid #888;
  line-height: 1.875em;
  margin-left: -6px;
  padding-left: 5px;
  position: absolute;
  top: 0;
}
td {
  border-color: #888;
  border-style: solid;
  border-width: 0 1px 2px 1px;
  padding: 5px;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width"/>
  <title>Title</title>
</head>
<body>
  <div class="table-container">
    <div></div>
    <table cellspacing="0">
      <thead>
        <tr>
          <th><div>Tag</div></th>
          <th><div>Datum</div></th>
          <th><div>Uhrzeit</div></th>
          <th><div>Liga</div></th>
          <th><div>Heimmannschaft</div></th>
          <th><div>Gastmannschaft</div></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Do.</td>
          <td>Heute</td>
          <td>19:45</td>
          <td>H1KK</td>
          <td class="teams">TuS Hiltrup V</td>
          <td class="teams">DJK Borussia Münster IV</td>
        </tr>
        <tr>
          <td>Fr.</td>
          <td>30.09.2016</td>
          <td>19:30</td>
          <td>H3KK</td>
          <td class="teams">SC Westfalia Kinderhaus V</td>
          <td class="teams">DJK Borussia Münster VI</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>20:00 v</td>
          <td>HBL</td>
          <td class="teams">DJK Borussia Münster II</td>
          <td class="teams">1. TTC Münster III</td>
        </tr>
        <tr>
          <td>Sa.</td>
          <td>01.10.2016</td>
          <td>18:30</td>
          <td>HNRWL</td>
          <td class="teams">DJK Borussia Münster</td>
          <td class="teams">1. TTC Münster</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>18:30</td>
          <td>DNRWL</td>
          <td class="teams">DJK Borussia Münster</td>
          <td class="teams">TTC Werne 98</td>
        </tr>
        <tr>
          <td>Di.</td>
          <td>04.10.2016</td>
          <td>20:15</td>
          <td>H1KK</td>
          <td class="teams">DJK Borussia Münster IV</td>
          <td class="teams">1. TTC Münster VII</td>
        </tr>
        <tr>
          <td>Do.</td>
          <td>06.10.2016</td>
          <td>20:00</td>
          <td>H2KK</td>
          <td class="teams">1. FC Gievenbeck IV</td>
          <td class="teams">DJK Borussia Münster V</td>
        </tr>
        <tr>
          <td>Fr.</td>
          <td>07.10.2016</td>
          <td>19:30</td>
          <td>HKL</td>
          <td class="teams">DJK SC Nienberge</td>
          <td class="teams">DJK Borussia Münster III</td>
        </tr>
        <tr>
          <td>Sa.</td>
          <td>08.10.2016</td>
          <td>17:30 v</td>
          <td>HBL</td>
          <td class="teams">SC Westfalia Kinderhaus II</td>
          <td class="teams">DJK Borussia Münster II</td>
        </tr>
        <tr>
          <td></td>
          <td></td>
          <td>18:30</td>
          <td>DNRWL</td>
          <td class="teams">TSSV Bottrop</td>
          <td class="teams">DJK Borussia Münster</td>
        </tr>
        <tr>
          <td>So.</td>
          <td>09.10.2016</td>
          <td>10:00</td>
          <td>HNRWL</td>
          <td class="teams">GSV Fröndenberg</td>
          <td class="teams">DJK Borussia Münster</td>
        </tr>
      </tbody>
    </table>
  </div>
</body>
</html>


Remarks to your code

JS rewrite

Your entire JavaScript code could be as simple as

const thElements = document.getElementsByTagName('th'),
      tdElements = document.getElementsByTagName('td');

for (let i = 0; i < thElements.length; i++) {
  const widerElement = thElements[i].offsetWidth > tdElements[i].offsetWidth ? thElements[i] : tdElements[i],
        width        = window.getComputedStyle(widerElement).width;

  thElements[i].style.width = tdElements[i].style.width = width;
}

Unnecessary template literal and toString()

This

width[i] = `${tempResult.toString()}`;

is exactly the same as

width[i] = tempResult;

because tempResult is already a string and as such both template literal and toString() are redundant.

Code repetition

Instead of entire if else, you could decide whether to call getComputedStyle() with thElements or tdElements parameter based on the result of computation of thElements[i].offsetWidth > tdElements[i].offsetWidth. See above code rewrite to see it implemented.

getComputedStyle()'s second parameter

getComputedStyle()'s second parameter null can be fully omitted.

Unnecessary second loop and width[]

Instead of saving all widths to an array and then running one more loop through that array, you could just set the widths right away. That way you wouldn't need neither a second loop nor the width[] array. Right now the said loop makes your solution have the computational complexity of \$O(2n)\$, where \$n\$ is the thElements.length, while it could be \$O(n)\$.

Use const instead of let for array declaration

Since it's better to use const „by default”, this

let width = [];

should be

const width = [];

since the value of width itself doesn't change ― it's always the same array throughout the code; only those array's values change.

Window resizing scenario

This code doesn't account for possibility of viewport's scaling.

\$\endgroup\$
  • \$\begingroup\$ Thank you very much for your answer. There seems to be something wrong with your pure HTML+CSS solution: i.imgur.com/ERIOozL.jpg The third row is too small for its head element "uhrzeit" and some rows split into multiple lines. The js code is great though. There was so much redundant stuff because I just made it work, not more. What is the name of the function you used in the code repetition paragraph? \$\endgroup\$ – Tweakimp Apr 19 '17 at 2:34
  • \$\begingroup\$ @Tweakimp: Splitting into multiple lines itself is wanted, it occurs when there is no width left to distribute. However, headers can indeed overflow as you showed in the image. If you want to get rid of that problem, ensure that there is at least one non-breaking „word” in at least one cell in the same column as the problematic header. You can do it for example by adding dummy non-breaking spaces at the end of one such cell. If you want to get rid of line-breaking too, add white-space: nowrap to styling of tds. The latter can make column wider and make the former problem disappear too. \$\endgroup\$ – Przemek Apr 19 '17 at 8:24
  • \$\begingroup\$ @Tweakimp: And regarding code repetition, I used ternary operator like this: const widerElement = thElements[i].offsetWidth > tdElements[i].offsetWidth ? thElements[i] : tdElements[i]. In other words, it's just a conditional statement that checks which width is greater and choose the appropriate element based on that. \$\endgroup\$ – Przemek Apr 19 '17 at 8:26
  • 1
    \$\begingroup\$ First css-only solution I've seen with non-fixed column widths that actually works! \$\endgroup\$ – Alexander Derck Mar 15 '18 at 11:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.