dokgu
dokgu

Reputation: 6040

HTML table with colspan width

I'm using a table that makes use of colspan extensively to display a report but I'm having a bit of an issue with the width of my td.

Here's a Fiddle

HTML

<table cellspacing='0'>
  <thead>
    <tr><th colspan='27'>&nbsp;</th></tr>
  </thead>
  <tfoot>
    <tr><td colspan='27'>&nbsp;</td></tr>
  </tfoot>
  <tbody>
    <tr class='odd label-first'>
      <td colspan='7'>Colony</td>
      <td colspan='20'>Test Colony</td>
    </tr>
    <tr class='even label-first'>
      <td colspan='7'>Full Nomenclature</td>
      <td colspan='20'>Hkfay38hfo9t<sup>hlFH</sup> 9t3P</td>
    </tr>
    <tr class='odd label-first'>
      <td colspan='7'>Investigator</td>
      <td colspan='20'>John Smith</td>
    </tr>
    <tr class="tr-th">
      <td colspan='7'>&nbsp;</td>
      <td colspan='10'>Breeding</td>
      <td colspan='10'>Non-breeding</td>
    </tr>
    <tr class="odd label-first">
      <td colspan='7'>Cages</td>
      <td colspan='10' class='c-align'>10</td>
      <td colspan='10' class='c-align'>2</td>
    </tr>
    <!-- <tr class="even label-first">
      <td colspan='7'>Estimated Housing Cost Per Month</td>
      <td colspan='2'>$105.00</td>
      <td colspan='4'>
        <span class='td-label'>Breeding Rate</span>0.16
      </td>
      <td colspan='4'>
        <span class='td-label'>Non-breeding Rate</span>0.95
      </td>
      <td colspan='10'>
        <span class='td-label'>Housing Cost Calculation</span>[ (10 * 0.16) * 30 days ] + [ (2 * 0.95) * 30 days ]
      </td>
    </tr> -->
    <tr class="tr-th">
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
      <td colspan='3' class="equal">Width</td>
    </tr>
    <tr class="odd">
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
      <td colspan='3' class="c-align equal">0</td>
    </tr>
  </tbody>
</table>

CSS

table {
  border-collapse: collapse;
}

th, td {
  padding: 7px;
  border: 1px solid #dddddd;
}

thead, .tr-th, tfoot {
  background-color: #003366;
  color: #ffffff;
  font-weight: bold;
}

tfoot td {
  padding: 0;
  font-size: 2px;
}

thead th, .tr-th td, tfoot td {
  border: 1px solid #003366;
  text-align: center;
}

.odd {
  background-color: #f9f9f9;
}

.even {
  background-color: #f2f2f2;
}

.label-first td:first-child {
  background-color: #eeeeee;
}

.c-align {
  text-align: center;
}

.td-label {
  display: block;
  font-size: 10px;
  font-weight: bold;
  font-style: italic;
}

.equal {
  min-width: 90px;
  width: 90px;
  max-width: 90px;
}

jQuery

$(document).ready(function() {
    $.each($("tr").not(".tr-th").find(".equal"), function(index, element) {
    $(this).html($(this).css("width"));
  });
});

I commented out the HTML that I think is causing the width issues. If you take a look at the Fiddle you'll see that the widths of all the td at the bottom of the table are all equal but if you add the tr that was commented out suddenly the td won't have equal widths.

I'm aware that I could avoid the use of colspan and just use it for when needed but for now let's stick with it.

Upvotes: 2

Views: 3471

Answers (1)

zer00ne
zer00ne

Reputation: 43880

Update

I know this is a totally old Q&A, but I happened upon it reviewed it and found it wanting.

  • Added the table
  • Changed the layout of table so that each colspan shares a denominator of 27
  • Added <input>s and <output>s that will calculate the quantity of cages?
  • Sectioned the table with <tbody>s so that each display of data and function were defined, isolated, and easily selectable (well to a point since each row had different number of colspans.)

What I've written previously still stands, except for what I've said about table-layout:fixed. I said it honors the width set for a <td> and it will until the width of the table itself exceeds the sum of all column widths. It still acts like a table thankfully. The thing that fixed table don't do is conform to it's content and distributes widths unevenly. Note in the demo the bottom row of 9 <td>s at 50px widths (give or take a pixel, I have no idea why).

  • Each <td> is width:30px [<-+30->] = 30px
  • Each <td> is padding:5px [<-+5<-+30->+5->] = 40px
  • Each <td> has 10px unaccounted for [[<-+5<-+30+10->+5->] 50px

That 10px is because the expected 40px width for each does not add up to the width of the table at 450px. So it distributes that extra 90px evenly. If the table wasn't fixed, there would be an uneven distribution of widths. To see this live compare Demo 1 and Demo 2.


Old

The default box-sizing value for Firefox is content-box, that means that while you are expecting a 90px wide <td> you actually see a 117px wide <td> because content-box only measures an element's width and height properties includes only the content. Border, padding, and margin are not included in the measurements.

We as human beings feel more comfortable measuring what we perceive which includes border and padding (not really margin since that's presented as empty space between elements. The border-box model is what we naturally assume is being implemented. border-box includes an element's content, borders, and padding. The only thing it doesn't include is margins (what we normally don't count in a measurement anyways.)

What you need to do is set up your defaults before you start a project that is heavily involved with dimensions that all intersect with each other at such tight measurements. 27 columns means that borders can add up to a minimum of about an extra 54px horizontally and vertically.

The following are style defaults I'll start of with the important properties are marked with asterisks and daggers:

Details are commented in demos

Demo 1

$(document).ready(function() {

  /* First function is a modified version of
  || The original one. Each <td> targeted will
  || display it's width
  */
  $('#measure tr:last-of-type td').each(function(idx, ele) {
    $(this).html($(this).css("width"));
  });
  /* This function delegates the input event
  || on all <input>s
  */
  $('input').on('input', function(e) {
    /* Get this (i.e. <input> triggered) node's
    || parent <td>, then...
    || ...get all of that <td>'s sibling <td>
    || that follow it, then...
    || ...get the <output> in each of those <td>
    */
    var outs = $(this).parent('td').nextAll('td').find('output');
    // Store the value of the first <output>
    var rate = outs[0].value;
    // Get the value of this and multiply it with rate
    var cost = this.value * rate;
    /* The value of the second <output> is
    || the product of this value, rate and 30
    || formated to the hundredths (currency)
    */
    outs[1].value = (cost * 30).toFixed(2);
    // Calculate each subtotal to display it
    var bnTotal = Number($('#bTotal').val()) + Number($('#nTotal').val());
    // Then do so for the total as well
    $('#bnTotal').val(bnTotal.toFixed(2));
  });
});
html {
  /* border-box is the best box modal to use */
  box-sizing: border-box;
  /* font-size set at root can be easily 
  || referenced by any other font-size on the page
  */
  font: 400 62.5%/1.428 Tahoma;
  /* Both dimensions set at 100% x 100% allows
  || the children elements a solid reference 
  || when relative measurement is used.
  */
  height: 100%;
  width: 100%;
}


/* Pseudo-elements must be explicitly set in
|| cases were the universal selector is used *
|| I think it's because they are not part of the DOM
*/

*,
*:before,
*:after {
  box-sizing: inherit;
  /* Hard reset on:
  || - margin so positioning isn't hindered. 
  || - padding element size is can be easily discerned
  || - border same reason as padding
  || - outline it doesn't affect dimensions like
  ||   border but can be easily confused with border.
  ||   Add outlines back afterwards
  */
  margin: 0px;
  padding: 0px;
  border: 0px none transparent;
  outline: 0px none transparent;
}

body {
  position: relative;
  font-size: 1rem;
  height: 100%;
  width: 100%;
  overflow: scroll;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

table {
  /* Ensures there's no space between <td> */
  border-collapse: collapse;
  /* As above */
  border-spacing: 0;
  /* Disable table feature responsible for evenly
  || distributing <td> width and compliance to
  || content. If a length is given on <td>, <th>,
  || or <table>, it will adhere to it.
  */
  table-layout: fixed;
  /* Set the width of table that share a common
  || denominator with the width of a <td>
  */
  width: 450px;
  margin: 20px auto;
}

th,
td {
  padding: 5px;
  outline: 1px solid #dddddd;
  width: 30px;
}

tbody tr td:first-of-type {
  background-color: #556;
  color: #eee;
}

tr:nth-of-type(2n+1) td {
  background-color: #f9f9f9;
}

tr:nth-of-type(2n+2) td {
  background-color: #f2f2f2;
}

code {
  display: block;
  margin: -15px 0;
  padding: 0;
}

pre {
  font: 100 .5rem/1.2 Consolas;
}

input,
output {
  font: inherit;
  display: block;
  width: 100%;
  height: 100%;
  text-align: right;
}

input:hover {
  outline: 1px solid #0e0;
  color: #0e0;
  background: #111;
  cursor: pointer;
}

output::before {
  content: ' $'
}

thead th,
tfoot td {
  background-color: #003366;
  color: #ffffff;
  font-weight: bold;
  outline: 1px solid #003366;
}

#measure td {
  text-align: center
}

#cost tr:first-child td {
  background: rgba(51, 51, 51, .2);
}
<!doctype html>
<html>

<head>
  <meta charset='utf-8'>
  <style>

  </style>
</head>

<body>

  <table>

    <thead>
      <tr>
        <th colspan='27'>&nbsp;</th>
      </tr>
    </thead>

    <tbody id='profile'>
      <tr>
        <td colspan='6'>Colony</td>
        <td colspan='21'>Test Colony</td>
      </tr>
      <tr>
        <td colspan='6'>Nomenclature</td>
        <td colspan='21'>Hkfay38hfo9t<sup>hlFH</sup> 9t3P</td>
      </tr>
      <tr>
        <td colspan='6'>Investigator</td>
        <td colspan='21'>John Smith</td>
      </tr>
    </tbody>

    <tbody id='info'>
      <tr>
        <td colspan='6'>Estimated Housing Cost Per Month</td>
        <td colspan='3'>$105</td>

        <td colspan='18'>
          <code><pre>
Housing Cost Calculation
[(bN * bCage)*(bRate * 30 DAYS)]
             +
[(nN * nCage)*(nRate * 30 DAYS)]
</pre></code>
        </td>
      </tr>
    </tbody>

    <tbody id='cost'>
      <tr>
        <td colspan='6'>&nbsp;</td>
        <td colspan='9'>Type</td>
        <td colspan='3' style='text-align:center;'>Qty</td>
        <td colspan='3' style='text-align:center;'>Rate
        </td>
        <td colspan='6' style='text-align:center;'>Monthly Cost</td>
      </tr>
      <tr>
        <td colspan='6' rowspan='3'>Cages</td>
        <td colspan='9' style='background:#556;color:#eee'>Breeding</td>
        <td colspan='3'>
          <input id='bN' type='number' min='0'>
        </td>
        <td colspan='3'>
          <output id='bRate'>.16</output>
        </td>
        <td colspan='6'>
          <output id='bTotal' for='bN'></output>
        </td>
      </tr>
      <tr>
        <td colspan='9'>Non-breeding</td>
        <td colspan='3'>
          <input id='nN' type='number' min='0'>
        </td>
        <td colspan='3'>
          <output id='nRate'>.95</output></td>
        <td colspan='6'>
          <output id='nTotal' for='nN'></output>
        </td>
      </tr>
      <tr>
        <td colspan='15'>Total Housing Cost Per Month</td>
        <td colspan='6'>
          <output id='bnTotal' for='bTotal nTotal'></output>
        </td>
      </tr>
    </tbody>

    <tbody id='measure'>
      <tr>
        <td colspan='3' style='background:#f9f9f9;color:#000'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
      </tr>
      <tr>
        <td colspan='3' style='background:#f9f9f9;color:#000'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
      </tr>
    </tbody>

    <tfoot>
      <tr>
        <td colspan='27' style='background:#036;'>
          &nbsp;
        </td>
      </tr>
    </tfoot>

  </table>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

🗡These properties are reset to 0 on everything so caveat emptor

*The root(<html>) is set to box-sizing:border-box, then everything else is set to inherit border-box from the root, but you'll have the option of changing anything to content-box and it's children will naturally inherit content-box as well. It's very doubtfully you'll need to though.

In your specific case you expect each .equal to be 90px wide no more, no less (but that's not the behavior you'll get from a default <td>, we'll address that issue afterwards). If you look at a td.equal in devtools...it's @ 117px wide. I tried calculating the 27px difference but I could only explain 16px, I'll explain why there's an extra 11px unaccounted for soon. Here's a typical <td> in your table:

  width: 90px;
  padding: 7px x 2 (padding-right/padding-left)
  border: 1px x 2  (border-right/border-left)

So so far there's a minimum of 16px for every td.equal and it seems Firefox handles the extra sloppily and doesn't distribute the extra width very well, but Chrome on the other hand seems to do what's expected of a default table which is to automatically distribute the widths as evenly as possible. That's why the 90px width is not honored. So if you even used box-sizing:border-box (which you should) you will still have <td> with a mind of their own.

table-layout is a property that dictates how a table should handle it's content. There's 2 values that you can use:

  • auto: This is default behavior of tables, which is it will stretch to content and distribute widths between the <td> as it sees fit. It will disregard fixed measurements like 90px. This probably explains the extra 11px.

  • fixed: This behavior is more manageable. If you set a column to be 90px wide, the table will honor that 90px.

So to recap:

Use border-box for everything

html {
  box-sizing: border-box;*
  ....
}

*,
*:before,
*:after {
  box-sizing: inherit;
 ...
}

Use table-layout:fixed

References

MDN-box-sizing

CSS-Tricks-box-sizing

MDN-table-layout

Demo 2

[Note the bottom row of 9 cells, you'll see the 3rd cell greedily eating any extra width]

$(document).ready(function() {

  /* First function is a modified version of
  || The original one. Each <td> targeted will
  || display it's width
  */
  $('#measure tr:last-of-type td').each(function(idx, ele) {
    $(this).html($(this).css("width"));
  });
  /* This function delegates the input event
  || on all <input>s
  */
  $('input').on('input', function(e) {
    /* Get this (i.e. <input> triggered) node's
    || parent <td>, then...
    || ...get all of that <td>'s sibling <td>
    || that follow it, then...
    || ...get the <output> in each of those <td>
    */
    var outs = $(this).parent('td').nextAll('td').find('output');
    // Store the value of the first <output>
    var rate = outs[0].value;
    // Get the value of this and multiply it with rate
    var cost = this.value * rate;
    /* The value of the second <output> is
    || the product of this value, rate and 30
    || formated to the hundredths (currency)
    */
    outs[1].value = (cost * 30).toFixed(2);
    // Calculate each subtotal to display it
    var bnTotal = Number($('#bTotal').val()) + Number($('#nTotal').val());
    // Then do so for the total as well
    $('#bnTotal').val(bnTotal.toFixed(2));
  });
});
html {
  /* border-box is the best box modal to use */
  box-sizing: border-box;
  /* font-size set at root can be easily 
      || referenced by any other font-size on the page
      */
  font: 400 62.5%/1.428 Tahoma;
  /* Both dimensions set at 100% x 100% allows
      || the children elements a solid reference 
      || when relative measurement is used.
      */
  height: 100%;
  width: 100%;
}


/* Pseudo-elements must be explicitly set in
    || cases were the universal selector is used *
    || I think it's because they are not part of the DOM
    */

*,
*:before,
*:after {
  box-sizing: inherit;
  /* Hard reset on:
      || - margin so positioning isn't hindered. 
      || - padding element size is can be easily discerned
      || - border same reason as padding
      || - outline it doesn't affect dimensions like
      ||   border but can be easily confused with border.
      ||   Add outlines back afterwards
      */
  margin: 0px;
  padding: 0px;
  border: 0px none transparent;
  outline: 0px none transparent;
}

body {
  position: relative;
  font-size: 1rem;
  height: 100%;
  width: 100%;
  overflow: scroll;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

table {
  /* Ensures there's no space between <td> */
  border-collapse: collapse;
  /* As above */
  border-spacing: 0;
  /* Disable table feature responsible for evenly
      || distributing <td> width and compliance to
      || content. If a length is given on <td>, <th>,
      || or <table>, it will adhere to it.
      */
  ||||||||||||||||||||||||||||||||||||||||||||||||||||||/* Everything about demo 2 is exactly like demo 1, except for the table-layout:fixed is now auto. Note the bottom row has redistributed unevenly (3rd <td> is 61px wide)
*/
  table-layout: auto;
  /* Set the width of table that share a common
      || denominator with the width of a <td>
      */
  width: 450px;
  margin: 20px auto;
}

th,
td {
  padding: 5px;
  outline: 1px solid #dddddd;
  width: 30px;
}

tbody tr td:first-of-type {
  background-color: #556;
  color: #eee;
}

tr:nth-of-type(2n+1) td {
  background-color: #f9f9f9;
}

tr:nth-of-type(2n+2) td {
  background-color: #f2f2f2;
}

code {
  display: block;
  margin: -15px 0;
  padding: 0;
}

pre {
  font: 100 .5rem/1.2 Consolas;
}

input,
output {
  font: inherit;
  display: block;
  width: 100%;
  height: 100%;
  text-align: right;
}

input:hover {
  outline: 1px solid #0e0;
  color: #0e0;
  background: #111;
  cursor: pointer;
}

output::before {
  content: ' $'
}

thead th,
tfoot td {
  background-color: #003366;
  color: #ffffff;
  font-weight: bold;
  outline: 1px solid #003366;
}

#measure td {
  text-align: center
}

#cost tr:first-child td {
  background: rgba(51, 51, 51, .2);
}
<!doctype html>
<html>

<head>
  <meta charset='utf-8'>
  <style>

  </style>
</head>

<body>

  <table>

    <thead>
      <tr>
        <th colspan='27'>&nbsp;</th>
      </tr>
    </thead>

    <tbody id='profile'>
      <tr>
        <td colspan='6'>Colony</td>
        <td colspan='21'>Test Colony</td>
      </tr>
      <tr>
        <td colspan='6'>Nomenclature</td>
        <td colspan='21'>Hkfay38hfo9t<sup>hlFH</sup> 9t3P</td>
      </tr>
      <tr>
        <td colspan='6'>Investigator</td>
        <td colspan='21'>John Smith</td>
      </tr>
    </tbody>

    <tbody id='info'>
      <tr>
        <td colspan='6'>Estimated Housing Cost Per Month</td>
        <td colspan='3'>$105</td>

        <td colspan='18'>
          <code><pre>
    Housing Cost Calculation
    [(bN * bCage)*(bRate * 30 DAYS)]
                 +
    [(nN * nCage)*(nRate * 30 DAYS)]
    </pre></code>
        </td>
      </tr>
    </tbody>

    <tbody id='cost'>
      <tr>
        <td colspan='6'>&nbsp;</td>
        <td colspan='9'>Type</td>
        <td colspan='3' style='text-align:center;'>Qty</td>
        <td colspan='3' style='text-align:center;'>Rate
        </td>
        <td colspan='6' style='text-align:center;'>Monthly Cost</td>
      </tr>
      <tr>
        <td colspan='6' rowspan='3'>Cages</td>
        <td colspan='9' style='background:#556;color:#eee'>Breeding</td>
        <td colspan='3'>
          <input id='bN' type='number' min='0'>
        </td>
        <td colspan='3'>
          <output id='bRate'>.16</output>
        </td>
        <td colspan='6'>
          <output id='bTotal' for='bN'></output>
        </td>
      </tr>
      <tr>
        <td colspan='9'>Non-breeding</td>
        <td colspan='3'>
          <input id='nN' type='number' min='0'>
        </td>
        <td colspan='3'>
          <output id='nRate'>.95</output></td>
        <td colspan='6'>
          <output id='nTotal' for='nN'></output>
        </td>
      </tr>
      <tr>
        <td colspan='15'>Total Housing Cost Per Month</td>
        <td colspan='6'>
          <output id='bnTotal' for='bTotal nTotal'></output>
        </td>
      </tr>
    </tbody>

    <tbody id='measure'>
      <tr>
        <td colspan='3' style='background:#f9f9f9;color:#000'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
        <td colspan='3'>Width</td>
      </tr>
      <tr>
        <td colspan='3' style='background:#f9f9f9;color:#000'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
        <td colspan='3'>0</td>
      </tr>
    </tbody>

    <tfoot>
      <tr>
        <td colspan='27' style='background:#036;'>
          &nbsp;
        </td>
      </tr>
    </tfoot>

  </table>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Upvotes: 1

Related Questions