Matching table height for nested HTML tables - javascript

I am trying to nest tables within a table row, while maintaining the appearance of a single table, as shown in the example below (a table with a single row with a data value and two nested tables, one 2x2 and the other 3x3) :
This is just an example; the actual table has significantly more rows and columns. I want to use tables because of the natural reflowing of column widths and row heights to fit the table data without having to worry about the container size (i.e. table width = 100%).
The problem I am having is that the tallest table sets the row height, but the other tables don't expand to fill that height, so the internal borders don't stretch from top to bottom as shown in the result of this snippet:
.display {
border-collapse: collapse;
}
.display, .display td, .display th {
border: 1px solid black;
}
.subtable {
border-collapse: collapse;
}
.subtable td {
border-top: 0;
border-bottom: 1px solid black;
border-left: 0;
border-right: 1px solid black;
}
.subtable tr td:last-of-type {
border-top: 0;
border-bottom: 1px solid black;
border-left: 0;
border-right: 0;
}
.subtable tr:last-of-type td {
border-top: 0;
border-bottom: 0;
border-left: 0;
border-right: 1px solid black;
}
.subtable tr:last-of-type td:last-of-type {
border: 0;
}
td {
padding: 5px;
}
td.d-subtable {
padding: 0;
}
<table class="display" cellpadding="0">
<tr><th>Customer</th><th>Items</th><th>Payments</th></tr>
<tr><td>Customer Name</td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>Item1</td><td>5</td><td>$400.00</td></tr><tr><td>Item2</td><td>10</td><td>$200.00</td></tr><tr><td>Item3</td><td>2</td><td>$500.00</td></tr></table></td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>12 Sep 2018</td><td>$3,000.00</td></tr><tr><td>18 Sep 2018</td><td>$2,000.00</td></tr></table></td>
</tr>
</table>
Now I know I can resolve this problem using rowspan (and that is how I am currently solving the problem) but that requires deciding in advance which rows to line up and that can lead to issues such as that generated by the below snippet, where it would clearly be better if I'd applied rowspan="2" to the first row (instead of the last row) of the table with 2 rows:
td {
border: 1px solid black;
}
table {
border-collapse: collapse;
width: 500px;
}
<table cellpadding="5">
<tr><td rowspan="3">x</td>
<td>problem when you have some really long text in the first row</td><td>p</td><td>a</td><td>b</td><td>c</td></tr><tr><td rowspan="2">z</td><td rowspan="2">q</td><td>d</td><td>e</td><td>f</td></tr><tr><td>g</td><td>some other really long text</td><td>i</td>
</tr>
</table>
I would prefer the above table to look like this:
Is there a way to achieve what I want using HTML/CSS? There are a lot of rows in the table so I'd prefer the browser sort it out before rendering. However if it's not possible I'm open to a Javascript/JQuery solution.
Update
Although I did find a workable solution at the time (see my posted answer) I have since encountered some situations where setting the widths of the columns in advance (even as percentages) was difficult owing to not being able to anticipate all the possible data to be displayed. So I'm hoping to find an answer that doesn't rely on doing that.
Since I didn't make it as clear as I should have, I have multiple rows in which I want to nest tables, keeping the heights matched as well as the column widths. For example, for two rows, I would like to be able to create a layout like this:
Where with raw table HTML the result looks like this:
.display {
border-collapse: collapse;
}
.display, .display td, .display th {
border: 1px solid black;
}
.subtable {
border-collapse: collapse;
}
.subtable td {
border-top: 0;
border-bottom: 1px solid black;
border-left: 0;
border-right: 1px solid black;
}
.subtable tr td:last-of-type {
border-top: 0;
border-bottom: 1px solid black;
border-left: 0;
border-right: 0;
}
.subtable tr:last-of-type td {
border-top: 0;
border-bottom: 0;
border-left: 0;
border-right: 1px solid black;
}
.subtable tr:last-of-type td:last-of-type {
border: 0;
}
td {
padding: 5px;
}
td.d-subtable {
padding: 0;
}
<table class="display" cellpadding="0">
<tr><th>Customer</th><th>Items</th><th>Payments</th></tr>
<tr><td>Customer 1</td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>Item1</td><td>5</td><td>$400.00</td></tr><tr><td>Item2</td><td>100</td><td>$20.00</td></tr><tr><td>Item3</td><td>2</td><td>$500.00</td></tr></table></td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>12 Sep 2018</td><td>$3,000.00</td></tr><tr><td>18 Sep 2018</td><td>$2,000.00</td></tr></table></td>
</tr>
<tr><td>Customer 304</td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>Item4</td><td>5</td><td>$6.00</td></tr></table></td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td>20 Sep 2018</td><td>$4.00</td></tr><tr><td>27 Sep 2018</td><td>$26.00</td></tr></table></td>
</tr>
</table>

I would suggest the use of flex to achieve your need. Flex is very powerful for this kind of layout as we can easily let the content guide it. Please, see the attached snippet. It is purely made from HTML and CSS. No fixed sizes and no Javascript required.
(old snippet)
.outer {
display: flex;
flex-wrap: wrap;
/* For demo purposes */
max-width: 500px;
margin: 20px auto;
border-left: 1px solid black;
border-top: 1px solid black;
}
.column {
flex: 1 1 auto;
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.row {
display: flex;
flex-direction: row;
flex: 1 1 auto;
}
.inner {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.item {
border-right: 1px solid black;
border-bottom: 1px solid black;
text-align: center;
padding: 3px;
}
.item.heading {
font-weight: bold;
}
.item:not(.heading) {
flex: 1 1 auto;
justify-content: center;
align-items: center;
display: flex;
}
.fixed .narrow {
flex-basis: 20px;
}
<div class="outer">
<div class="column">
<div class="item heading">Customer</div>
<div class="item">
<span>Customer Name</span>
</div>
</div>
<div class="column">
<div class="item heading">Items</div>
<div class="inner fixed">
<div class="row">
<div class="item">Item1</div>
<div class="item narrow">5</div>
<div class="item last">$400.00</div>
</div>
<div class="row">
<div class="item">Item2</div>
<div class="item narrow">10</div>
<div class="item last">$200.00</div>
</div>
<div class="row">
<div class="item">Item3</div>
<div class="item narrow">2</div>
<div class="item last">$500.00</div>
</div>
</div>
</div>
<div class="column">
<div class="item heading">Payments</div>
<div class="inner">
<div class="row">
<div class="item">12 sep 2018</div>
<div class="item">$3,000.00</div>
</div>
<div class="row">
<div class="item">
18 sep 2018
</div>
<div class="item">
$2,000.00
</div>
</div>
</div>
</div>
</div>
Update: Changed according to your comment/answer. It somewhat depends on your HTML structure. I had to move the headings to its own ".row.row-item" and therefor needed to set a flex-basis to align the columns. This can be extended with multiple ".row.row-item". See snippet below.
.outer {
display: flex;
flex-wrap: wrap;
/* For demo purposes */
max-width: 600px;
margin: 20px auto;
border-left: 1px solid black;
border-top: 1px solid black;
}
.column {
flex: 1 1 33.33%;
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
.row {
display: flex;
flex-direction: row;
flex: 1 1 auto;
}
.row-item {
flex-basis: 100%;
}
.inner {
flex: 1 1 auto;
display: flex;
flex-direction: column;
}
.item {
border-right: 1px solid black;
border-bottom: 1px solid black;
text-align: center;
}
.item.heading {
font-weight: bold;
flex: 1 1 33.33%;
}
.item:not(.heading) {
flex: 1 1 33.33%;
justify-content: center;
align-items: center;
display: flex;
}
.fixed .narrow {
flex: 1 1 20px;
}
<div class="outer">
<div class="row row-item">
<div class="item heading">Customer</div>
<div class="item heading">Items</div>
<div class="item heading">Payments</div>
</div>
<div class="row row-item">
<div class="column">
<div class="item">
<span>Customer 1</span>
</div>
</div>
<div class="column">
<div class="inner fixed">
<div class="row">
<div class="item">Item1</div>
<div class="item narrow">5</div>
<div class="item last">$400.00</div>
</div>
<div class="row">
<div class="item">Item2</div>
<div class="item narrow">10</div>
<div class="item last">$200.00</div>
</div>
<div class="row">
<div class="item">Item3</div>
<div class="item narrow">2</div>
<div class="item last">$500.00</div>
</div>
</div>
</div>
<div class="column">
<div class="inner">
<div class="row">
<div class="item">12 sep 2018</div>
<div class="item">$3,000.00</div>
</div>
<div class="row">
<div class="item">
18 sep 2018
</div>
<div class="item">
$2,000.00
</div>
</div>
</div>
</div>
</div>
<div class="row row-item">
<div class="column">
<div class="item">
<span>Customer 304</span>
</div>
</div>
<div class="column">
<div class="inner fixed">
<div class="row">
<div class="item">Item4</div>
<div class="item narrow">5</div>
<div class="item last">$6.00</div>
</div>
</div>
</div>
<div class="column">
<div class="inner">
<div class="row">
<div class="item">20 sep 2018</div>
<div class="item">$4.00</div>
</div>
<div class="row">
<div class="item">
27 sep 2018
</div>
<div class="item">
$26.00
</div>
</div>
</div>
</div>
</div>
<div class="row row-item">
<div class="column">
<div class="item">
<span>Customer 605</span>
</div>
</div>
<div class="column">
<div class="inner fixed">
<div class="row">
<div class="item">Item5</div>
<div class="item narrow">50</div>
<div class="item last">$60.00</div>
</div>
<div class="row">
<div class="item">Item6</div>
<div class="item narrow">3</div>
<div class="item last">$260.00</div>
</div>
</div>
</div>
<div class="column">
<div class="inner">
<div class="row">
<div class="item">29 sep 2018</div>
<div class="item">$40.00</div>
</div>
<div class="row">
<div class="item">
30 sep 2018
</div>
<div class="item">
$206.00
</div>
</div>
</div>
</div>
</div>
</div>

I was eventually able to solve this by writing an onload function to find the maximum height of all the nested tables in a given row and then set the height of every nested table in that row to the same value.
window.onload=function () {
let rows = document.querySelectorAll('tr');
for (let r = 0; r < rows.length; r++) {
let subtables = rows[r].querySelectorAll('.subtable');
let maxHeight = 0;
for (let i = 0; i < subtables.length; i++) {
maxHeight = Math.max(maxHeight, subtables[i].clientHeight);
}
for (let i = 0; i < subtables.length; i++) subtables[i].style.height='' + maxHeight + 'px';
}
};
The only downside of this solution was that it meant I had to assign widths to the <td>s in the nested tables. However since I could use percentage widths this wasn't too big an issue for me:
<table class="display" cellpadding="0">
<tr><th>Customer</th><th>Items</th><th>Payments</th></tr>
<tr><td>Customer 1</td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td width="35%">Item1</td><td width="20%">5</td><td width="45%">$400.00</td></tr><tr><td>Item2</td><td>10</td><td>$200.00</td></tr><tr><td>Item3</td><td>2</td><td>$500.00</td></tr></table></td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td width="60%">12 Sep 2018</td><td width="40%">$3,000.00</td></tr><tr><td>18 Sep 2018</td><td>$2,000.00</td></tr></table></td>
</tr>
<tr><td>Customer 2</td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td width="35%">Item4</td><td width="20%">5</td><td width="45%">$600.00</td></tr></table></td>
<td class="d-subtable"><table class="subtable" cellpadding="0"><tr><td width="60%">20 Sep 2018</td><td width="40%">$4,000.00</td></tr><tr><td>27 Sep 2018</td><td>$2,000.00</td></tr></table></td>
</tr>
</table>
Final output:

This example is a responsive design which utilizes only HTML and nested CSS Grids inside of a Flexbox.
I took the liberty of adding headers and a pagination placeholder to your scenario. These may be removed without affecting the rest of the layout.
I have created a GitHub repo nested-CSS-Grid-and-Flexbox-Playground which contains an Angular application utilizing this layout with dynamic data as well as references I accumulated while researching this project.
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div class="container">
<div class="header">
<h1>Customers List</h1>
</div>
<div class="list">
<div class="customer-column-header">Customers</div>
<div class="invoice-column-header">Invoices</div>
<div class="payments-column-header">Payments</div>
<div class="customer-row">
<div class="customer-column">
<div>Acme Widget Manufacturing, Inc.</div>
</div>
<ul class="invoice-column">
<li class="invoice-row-header">
<div>Description</div>
<div>Quantity</div>
<div>Price</div>
</li>
<li class="invoice-row">
<div>Item 1</div>
<div>5</div>
<div>$400.00</div>
</li>
<li class="invoice-row">
<div>Item 2</div>
<div>10</div>
<div>$200.00</div>
</li>
<li class="invoice-row">
<div>Item 3</div>
<div>2</div>
<div>$500.00</div>
</li>
</ul>
<ul class="payment-column">
<li class="payment-row-header">
<div>Date</div>
<div>Amount</div>
</li>
<li class="payment-row">
<div>12 Sep 2018</div>
<div>$3,000.00</div>
</li>
<li class="payment-row">
<div>18 Sep 2018</div>
<div>$2,000.00</div>
</li>
<li class="payment-row">
<div>12 Sep 2018</div>
<div>$3,000.00</div>
</li>
<li class="payment-row">
<div>18 Sep 2018</div>
<div>$2,000.00</div>
</li>
</ul>
</div>
<div class="customer-row">
<div class="customer-column">
<div>Beta Company</div>
</div>
<ul class="invoice-column">
<li class="invoice-row-header">
<div>Description</div>
<div>Quantity</div>
<div>Price</div>
</li>
<li class="invoice-row">
<div>Item 1</div>
<div>5</div>
<div>$400.00</div>
</li>
<li class="invoice-row">
<div>Item 2</div>
<div>10</div>
<div>$200.00</div>
</li>
<li class="invoice-row">
<div>Item 3</div>
<div>2</div>
<div>$500.00</div>
</li>
</ul>
<ul class="payment-column">
<li class="payment-row-header">
<div>Date</div>
<div>Amount</div>
</li>
<li class="payment-row">
<div>12 Sep 2018</div>
<div>$3,000.00</div>
</li>
<li class="payment-row">
<div>18 Sep 2018</div>
<div>$2,000.00</div>
</li>
</ul>
</div>
</div>
<div class="pagination">
<p>Pagination Placeholder</p>
</div>
</div>
</body>
</html>
CSS
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
ul {
list-style-type: none;
}
.container {
width: 62.5rem;
max-width: 80rem;
margin: 0 auto;
background-color: lightgray;
display: flex;
flex-direction: column;
justify-content: center;
}
.header {
padding: .5rem;
background-color: darkgrey;
align-self: center;
}
.list {
background-color: darkcyan;
display: grid;
grid-template-columns: 1fr repeat(2, minmax(min-content, 1fr));
}
.customer-column-header {
grid-column: 1 / 2;
}
.invoice-column-header {
grid-column: 2 / 3;
}
.payments-column-header {
grid-column: 3 / 4;
}
.customer-column-header,
.invoice-column-header,
.payments-column-header {
padding: .5rem;
text-align: center;
}
.customer-row {
border-left: 1px solid;
border-top: 1px solid;
background-color: orangered;
grid-column: 1 / -1;
display: grid;
grid-template-columns: 1fr repeat(2, minmax(min-content, 1fr));
}
.customer-column {
grid-column: 1 / 2;
}
.invoice-column {
grid-column: 2 / 3;
}
.payment-column {
grid-column: 3 / 4;
}
.customer-column {
align-self: center;
justify-self: right;
}
.customer-column > div {
padding: 1rem;
}
.invoice-column {
border-left: 1px solid;
}
.invoice-row-header {
background-color: darkcyan;
border-bottom: 1px solid;
display: grid;
grid-template-columns: repeat(3, minmax(6rem, 1fr));
justify-self: stretch;
}
.invoice-row-header > div {
text-align: right;
padding: .5rem;
justify-self: stretch;
align-self: stretch;
}
.invoice-row-header > div:nth-child(2) {
border-left: 1px solid;
border-right: 1px solid;
}
.invoice-row {
border-bottom: 1px solid;
display: grid;
grid-template-columns: repeat(3, minmax(6rem, 1fr));
justify-items: stretch;
align-items: stretch;
}
.invoice-row > div {
text-align: right;
padding: .5rem;
justify-self: stretch;
align-self: stretch;
}
.invoice-row div:nth-child(2) {
border-left: 1px solid;
border-right: 1px solid;
}
.payment-column {
border-left: 1px solid;
}
.payment-row-header {
background-color: darkcyan;
border-bottom: 1px solid;
display: grid;
grid-template-columns: repeat(2, minmax(6rem, 1fr));
justify-self: stretch;
}
.payment-row-header > div {
text-align: right;
padding: .5rem;
justify-self: stretch;
align-self: stretch;
}
.payment-row-header > div:nth-child(1) {
border-right: 1px solid;
}
.payment-row {
border-bottom: 1px solid;
display: grid;
grid-template-columns: repeat(2, minmax(6rem, 1fr));
justify-items: stretch;
align-items: stretch;
}
.payment-row > div {
text-align: right;
padding: .5rem;
justify-self: stretch;
align-self: stretch;
}
.payment-row > div:nth-child(1) {
border-right: 1px solid;
}
.pagination {
padding: .5rem;
background-color: darkgrey;
align-self: center;
}
#media only screen and (min-width: 120rem) {
.container {
max-width: 80rem;
margin: 0;
}
}
#media only screen and (max-width: 80rem) {
.container {
max-width: 62.5rem;
margin: 0 auto;
}
}
#media only screen and (max-width: 62.5rem) {
.container {
max-width: 45rem;
margin: 0 auto;
}
.list {
grid-template-columns: repeat(autofit, 100%);
}
.customer-column-header,
.invoice-column-header,
.payments-column-header {
grid-column: 1 / -1;
}
.customer-row {
grid-template-columns: repeat(autofit, 100%);
justify-content: center;
}
.customer-row .customer-column,
.customer-row .invoice-column,
.customer-row .payments-column {
grid-column: 1 / -1;
}
.customer-column {
justify-self: center;
}
}
#media only screen and (max-width: 45rem) {
.container {
width: 100%;
margin: 0 auto;
}
}
cref:https://codepen.io/randydaddis/pen/LqEjEg

No rowspans or colspans Were Harmed Making This Table
The following is a diagram of the table sans thead:
The term "column" is termed loosely. For example: column td.items refers to all
td.items as a group.
There is the main table...
...and then a tbody for each customer row. Having multiple tbodies is valid.
Next is a tr and then...
...a td. This td is the columns: .customer, .items, and .payments.
Within each td is a sub-table and sub-tbody.
The first column td.customer is limited to one sub-row since the content for each
tbody.row is just a
customer's name. The cells for this column have the properties
that control tbody.row height
(see comments in demo).
The second column td.items can have multiple sub-rows, the demo features from 1 to 3
sub-rows.
The last column td.payments can have multiple sub-rows as well, the demo features
2 sub-rows each.
Demo
Details are commented in demo
The demo is responsive (might need MQ for edge cases and mobile devices).
Valid HTML/CSS and semantically sound.
No JavaScript/jQuery.
Built purely with <table>, <thead>, <tbody>, <tr>, <th>, and <td> tags (not even a div or span).
* { border: 0px none transparent; padding: 0px; margin: 0px; }
/*
|- table-layout: fixed;
|| Gives you more control over table behavior such as setting column widths and
|| table adhering to them.
===
|- height: 100%; width: 100%;
|| Applied to table.main as well as the nested tables. It facilitates all of
|| the nested tables to stretch evenly within tbody.row.
*/
table { border-collapse: collapse; border: 2px solid #000;
table-layout: fixed; height: 100%; width: 100%; }
table table { border: 1px solid #000; }
.main { min-height: 40vh; }
th { border-bottom: 2px solid #000; outline: 1px solid #000; }
/*
|| This rule set determines content wrapping behavior within each cell.
*/
td td { border: 1px solid #000; padding: 0px 5px; word-wrap: break-word;
word-break:break-word; overflow: hidden}
.items td:first-of-type { width: 30%; }
.items td:nth-of-type(2) { width: 25%; text-align: right; }
.items td:last-of-type { width: 45%; text-align: right; }
.payments td:last-of-type { text-align: right; }
.customer,
.items,
.payments { border: 0px none transparent; padding: 0px; }
th:first-of-type { border-left: 3px solid #000; width: 20%; }
th:nth-of-type(2) { width: 40%; }
th:last-of-type { border-right: 3px solid #000; width: 40%; }
/*
|| This allows the tr.row to always be equal in height as well as being
|| responsive. This column was the best choice as the one to stabilize height
|| since the content probably changes the least and it has the most space to use
|| hidden spacers (see next comment).
*/
.customer table { min-height: 20vh; }
/*
|| This sets a within the cells in the first column. Although vh units
|| are very responsive, they will start collapsing if the viewport height is
|| reduced disproportionately vs. viewport width. Thus, this pseudo-element
|| ensures a minimum height of 60px for both tr.row.
*/
.customer td::after { content: '\a0'; display: inline-block; width:0.5px;
min-height: 60px; line-height: 60px; vertical-align: middle; }
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
</head>
<body>
<table class='main'>
<thead>
<tr>
<th>Customer</th><th>Items</th><th>Payments</th>
</tr>
</thead>
<tbody class='row'>
<tr>
<td class='customer'>
<table><tr><td>
Customer 1
</td></tr></table>
</td>
<td class='items'>
<table>
<tr><td>Item1</td><td>5</td><td>$400.00</td></tr>
<tr><td>Item2</td><td>100</td><td>$20.00</td></tr>
<tr><td>Item3</td><td>2</td><td>$500.00</td></tr>
</table>
</td>
<td class='payments'>
<table>
<tr><td>12 Sep 2018</td><td>$3,000.00</td></tr>
<tr><td>18 Sep 2018</td><td>$2,000.00</td></tr>
</table>
</td>
</tr>
</tbody>
<tbody class='row'>
<tr>
<td class='customer'>
<table><tr><td>
Customer 304
</td></tr></table>
</td>
<td class='items'>
<table>
<tr><td>Item4</td><td>5</td><td>$6.00</td></tr>
</table>
</td>
<td class='payments'>
<table>
<tr><td>20 Sep 2018</td><td>$4.00</td></tr>
<tr><td>27 Sep 2018</td><td>$26.00</td></tr>
</table>
</td>
</tr>
</tbody>
<tbody class='row'>
<tr>
<td class='customer'>
<table><tr><td>
Customer 888
</td></tr></table>
</td>
<td class='items'>
<table>
<tr><td>Item5</td><td>50</td><td>$100.00</td></tr>
<tr><td>Item6</td><td>10</td><td>$500.00</td></tr>
</table>
</td>
<td class='payments'>
<table>
<tr><td>10 Nov 2018</td><td>$3,000.00</td></tr>
<tr><td>17 Nov 2018</td><td>$7,000.00</td></tr>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
References
table-layout
vh and vw
word-wrap: break-word; word-break:break-word;
:first-/:nth-/:last-of-type
::before/after

Here's a JavaScript solution that sets both the row heights and column widths automatically (does not require specifying column widths), and supports resizing.
The complete solution/demo is here: http://jsfiddle.net/ez0jqc1L/
The one caveat is this requires that you wrap the content of all <td> cells with <div class="content"></div> (see the jsfiddle for an example).
First, we fix the subtable heights:
let tbody = /* the tbody of the <table> containing subtables we are fixing */;
for(let r = 0; r < tbody.children.length; r++) {
let row = tbody.children[r];
let subtables = row.querySelectorAll('.subtable');
let maxHeight = 0;
for(let i = 0; i < subtables.length; i++) {
maxHeight = Math.max(maxHeight, subtables[i].clientHeight);
}
for(let i = 0; i < subtables.length; i++) {
subtables[i].style.height = maxHeight + 'px';
}
}
Then, we iterate over all subtable columns to compute the max width for each set of vertically adjacent cells (we store this into the maxColWidths array, each index of the array corresponds to the set of vertically adjacent cells):
let forEachSubtableColumn = function(f) {
for(let r = 0; r < tbody.children.length; r++) {
let row = tbody.children[r];
let subtables = row.querySelectorAll('.subtable');
let c = 0;
for(let i = 0; i < subtables.length; i++) {
let subtable = subtables[i];
if(subtable.children.length === 0 || subtable.children[0].children.length === 0) {
continue;
}
let stbody = subtable.children[0];
for(let j = 0; j < stbody.children.length; j++) {
let srow = stbody.children[j];
for(let k = 0; k < srow.children.length; k++) {
let td = srow.children[k];
f(c + k, td);
}
}
c += stbody.children[0].children.length;
}
}
};
let maxColWidths = [];
forEachSubtableColumn(function(c, td) {
if(c >= maxColWidths.length) {
maxColWidths.push(td.children[0].clientWidth);
} else {
maxColWidths[c] = Math.max(td.children[0].clientWidth, maxColWidths[c]);
}
});
Finally, we set the column widths. This is where the <div class="content"> wrapping is required, because setting the width of a <td> does not guarantee the browser will make it that exact width, but I can have this guarantee with an element having display: inline-block.
forEachSubtableColumn(function(c, td) {
td.children[0].style.width = maxColWidths[c] + 'px';
});
For resizing support we clear all the forced widths and heights so we can recompute them afterwards:
onresize = function() {
let tables = document.querySelectorAll('.subtable');
for(let i = 0; i < tables.length; i++) {
let table = tables[i];
table.style.removeProperty('height');
}
let tds = document.querySelectorAll('.subtable td');
for(let i = 0; i < tds.length; i++) {
let td = tds[i];
td.children[0].style.removeProperty('width');
}
updateTables();
};
EDIT: Corrected the onresize code.

The problem as given can actually be solved rather straightforward with nesting CSS grid layouts.
(If there there was colspan or rowspan that would require using their counterparts grid-column and grid-row.)
I simplified the HTML so the solution can be understood more clearly - though that loses the semantics of the table.
If it's desired to maintain the table markup then the intervening tags (e.g. the <tr>) need to be styled as display:contents to ensure they don't break up the layout (like here).
/* defining the grid */
.grid{display:grid;grid-gap:0 0;}
.grid-2col{grid-template-columns:auto auto;}
.grid-3col{grid-template-columns:auto auto auto;}
/* making it look like a table. */
.th{font-weight:bold;}
.grid>*{
border:1px solid black;
padding:5px;}
/* avoiding double borders. */
/* no double top border - first row can be recognized by class .th */
.grid>*{border-top:none;}
.th{border-top:1px solid black;}
/* no double side border - first column can be recognized modulo number of columns of grid. */
.grid>*{border-left:none;}
.grid-2col>*:nth-child(2n+1){border-left:1px solid black;}
.grid-3col>*:nth-child(3n+1){border-left:1px solid black;}
/* no padding in nested tables. */
.grid>.grid{border:none;padding:0px;}
.grid>.grid>*{border-left:none;border-top:none;}
<div class="grid grid-3col">
<span class="th">Customer</span>
<span class="th">Items</span>
<span class="th">Payments</span>
<span>Customer Name</span>
<div class="grid grid-3col">
<span>Item1</span>
<span>5</span>
<span>$400.00</span>
<span>Item2</span>
<span>10</span>
<span>$200.00</span>
<span>Item3</span>
<span>2</span>
<span>$500.00</span>
</div>
<div class="grid grid-2col">
<span>12 Sep 2018</span>
<span>$3,000.00</span>
<span>18 Sep 2018</span>
<span>$2,000.00</span>
</div>
</div>

The meaning of the <table> tag has changed between current HTML and older HTML. While <table> used to server more of a formatting function, in HTML5 it should only be used for a literal table. As others have mentioned, your setup doesn't really look like a table in the literal sense. So while using a bunch of nested <table> elements could accomplish what you want, it's probably not the most effective way.
You could accomplish this by just nesting a bunch of <div> elements along with some basic CSS. Here's an example:
<div style="width: 1000px; height: 600px; border: 1px solid #000;">
<div style="width: 100px; height: 600px; display: inline-block; border: 1px solid #000; margin: 0; padding: 0;">X</div>
<div style="width: 100px; height: 300px; display: inline-block; border: 1px solid #000; margin: 0; padding: 0;">Y</div>
<div style="width: 100px; height: 300px; display: inline-block; border: 1px solid #000; position: relative; top: 300px; margin: 0; padding: 0; left: -105px;">Z</div>
</div>
This will produce the X, Y, and Z parts of the display you describe above. The same technique can be expanded to build the rest of your display.
A few things to note:
This example uses PXs to determine width, which is really bad practice form a CSS perspective. It would be too big on mobile and too small on a large desktop. You could, however, adapt this technique to use a more adaptive format. This will add complexity to the solution though, hence why I chose to use PXs for the example.
This example uses inline CSS since it's the easiest way to show the solution. However, this is really bad practice, DON'T ACTUALLY DO THIS!
Hope this helps.

Online spreadsheet programs like Google Sheets make it look so easy, but when I first dove in, it was much more complicated. I gave each row a special attribute like row="r_1" and each subrow specific attributes like row="r_1" subrow="sr_1_a". onkeyup events or an onload triggered math to find all parent rows using querySelectorAll, then made sure their height was equal to all the subrow heights added up.

In my opinion I assume that it'd be hard to achieve what you want.
Reasons: td are nested in tr, so any change in height will affect height of of the associated row.
Suggestion: you can have five separate tabs or divs and have them displayed inline-block, then put your data in them.
If you use this suggestion, please first set a div that wraps the 5 columns and give it a font-size of 0px, then remember to change the font-size to something useful for the children.

Related

How to target first and last elements of a class

I have this simple markup of a table:
<div class="table">
<div class="table__headers"></div>
<div class="table__row"></div>
<div class="table__row"></div>
</div>
And I've styled it using this CSS:
<style lang="scss" scoped>
.grid {
display: grid;
grid-gap: 0.5rem;
}
.table {
&__headers,
&__row {
#extend .grid;
padding: 0.5rem 0.75rem;
:first-child {
text-align: start;
}
:last-child {
text-align: end;
}
p {
font-size: 0.8rem;
font-weight: bold;
text-align: center;
margin: 0;
}
}
&__row {
background: #f8f8fa;
}
}
</style>
My only issue is that I'm not able to specifically target the first and last table__row elements so I can style them differently.
The following does not work:
&__row:first-of-type {
border: solid 1px red;
}
&__row:last-of-type {
border: solid 1px blue;
}
last-of-type works but not first of type. Any help would be appreciated!
Thank you,
Here's a JavaScript solution:
let rows = document.querySelectorAll(".table__row"); // Get all rows into an "array-like" object
rows[0].classList.add("firstRow"); // Get and style first row
rows[rows.length-1].classList.add("lastRow"); // Get and style last row
.firstRow { background-color:red; }
.lastRow { background-color:green; }
<div class="table">
<div class="table__headers"></div>
<div class="table__row">blah</div>
<div class="table__row">blah</div>
<div class="table__row">blah</div>
<div class="table__row">blah</div>
<div class="table__row">blah</div>
<div class="table__row">blah</div>
</div>
The reason that :first-of-type is not working is because it's the functional equivalent of :nth-child(1), when what you need is :nth-child(2) due to the preceding <div> element.
These selectors are intended to work using elements (TagName) and not classes.

How to create 2 divs with equal height without bootstrap or jquery

I am trying to write a code is CSS and normal javascript but it won't work. Here is my code (HTML and CSS).
.wrapper{
height: 100%;
margin: 1.5rem 0 0 0;
display: flex;
}
.first{
vertical-align: top;
display: inline-block;
margin-right: 2rem;
flex: 1;
}
.second{
vertical-align:top;
display: inline-block;
flex: 1;
}
<div class="wrapper">
<div class="first">
<div class="times">
<div><h1>TIMES</h1><br></div>
<div class="space">
<h2>TIMES</h2>
<p>GESLOTEN</p>
</div>
<div class="space">
<h2>Dinsdag - Zaterdag</h2>
<p>09:30 UUR - 18:00 UUR</p>
</div>
</div>
</div>
<div class="second">
<div class="welcome">
<div><h1>WELKOM</h1></div>
<div><p>TEKST</p></div>
</div>
</div>
</div>
I have tried everything, at least I think I have.
The problem is that I can't fix this in CSS but I tried Java.
Still no success. Can someone please explain why I can't get it the same height.
It's a school project and I need to make a website from scratch.
Here is my full website source code: https://codepen.io/crosso_7/pen/VERrvQ
Both first and second divs are actually the same height - I just copied your snippet and applied a border around each div to see the issue and both divs are equal.
.wrapper{
height: 100%;
margin: 1.5rem 0 0 0;
display: flex;
}
.first{
vertical-align: top;
display: inline-block;
margin-right: 2rem;
flex: 1;
border: solid 1px blue;
}
.second{
vertical-align:top;
display: inline-block;
flex: 1;
border: solid 1px red;
}
<div class="wrapper">
<div class="first">
<div class="times">
<div><h1>TIMES</h1><br></div>
<div class="space">
<h2>TIMES</h2>
<p>GESLOTEN</p>
</div>
<div class="space">
<h2>Dinsdag - Zaterdag</h2>
<p>09:30 UUR - 18:00 UUR</p>
</div>
</div>
</div>
<div class="second">
<div class="welcome">
<div><h1>WELKOM</h1></div>
<div><p>TEKST</p></div>
</div>
</div>
</div>
You can do it using flexbox. Something like this code snippet.
.wrapper {
display: flex;
justify-content: space-between;
}
.wrapper div {
background: #000;
color: #fff;
flex: 1;
margin: 10px;
justify-content: center;
}
<div class='wrapper'>
<div>
<h1>1</h1>
<h2>asasasa</h2>
</div>
<div>2</div>
</div>
Is the first div the one thats higher? It's probably created by the padding from first div content vs seconds less content.
May have to set first and second div with a px or % height which are the same to make it equal.
Try using the code below in your .first and .second divs
flex: 1;
display: flex;

Div fade away on click and reveal other div

So let's say I have something like this:
body {
background: #ffffff;
}
.table {
display: table;
margin: 0px auto;
max-width: 400px
}
.row {
display: table-row;
width: 100%
}
.td1,
.td2,
.td3 {
display: table-cell;
border: 2px #aaaaaa solid;
padding: 15px;
background: #eeeeee;
font-size: 18px;
color: #000000;
width: 100%;
}
.td2,
.td3 {
border-top: none;
color: red;
}
<body>
<div class="table">
<div class="row">
<div class="td1">Here is some random text</div>
</div>
<div class="row">
<div class="td2">This is the text you see at first</div>
</div>
<div class="row">
<div class="td3">This is the text below the other div</div>
</div>
</div>
Now, what I would like to do is have the td2 text to show when you first see the page, but not the td3. Then when clicking the td2 div it makes a fadeout or slides upwards, and then reveal the td3 div and that text. In this particular case the div doesn't have to come back when re-clicking. It's just like a "one way ticket". Click, and it's gone forever.
What might be the easiest way to do this ?
You could use JQuery UI to get the fade effect, and register to click event on .td2 in order to update the DOM as per your requirement. Here's one way of doing it:
$(".td2").on("click", function(){
$(".td2").fadeOut();
$(".td3").fadeIn();
});
body {
background: #ffffff;
}
.table {
display: table;
margin: 0px auto;
max-width: 400px
}
.row {
display: table-row;
width:100%
}
.td1, .td2, .td3 {
display: table-cell;
border: 2px #aaaaaa solid;
padding: 15px;
background: #eeeeee;
font-size: 18px;
color: #000000;
width:100%;
}
.td2, .td3 {
border-top: none;
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery-ui.min.js"></script>
<div class="table">
<div class="row">
<div class="td1">Here is some random text</div>
</div>
<div class="row">
<div class="td2">This is the text you see at first</div>
</div>
<div class="row">
<div class="td3" style="display:none">This is the text below the other div</div>
</div>
</div>
$('.td2').on('click', function() {
$(this).fadeOut(200).promise()
.done(function() {
$('.td3').fadeIn(200);
});
});
.table {
display: table;
margin: 0px auto;
max-width: 400px
}
.row {
display: table-row;
width: 100%
}
.hide {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="table">
<div class="row">
<div class="td1">
Here is some random text
</div>
</div>
<div class="row">
<div class="td2">
This is the text you see at first
</div>
</div>
<div class="row">
<div class="td3 hide">
This is the text below the other div
</div>
</div>
</div>
You will need to learn some javascript and some jQuery for this ;)
Add this to your css:
.td3{
display: none;
}
And write this javascript:
$('.td2').on( "click", function(){
$('.td2').fadeOut();
$('.td3').fadeIn();
});

CSS child 100% INNER width

I have a situation where I need to specify that a child's width be 100% of the INNER width of the parent, not the outer width, meaning that the parent scrolls horizontally.
The situation looks similar to this:
http://codepen.io/anon/pen/ygqPZG?editors=1100
HTML
<div id='parent'>
<table id='child1'>
<colgroup>
<col width='400px'></col>
</colgroup>
<tbody>
<tr>
<td>Child1withareallyreallylongtitle</td>
</tr>
</tbody>
</table>
<div id='child2'>
<p>Child 2</p>
</div>
</div>
CSS
#parent {
margin: 16px;
border: 1px solid black;
box-sizing: border-box;
overflow-x: auto;
}
#child1 {
width: 100%;
border: 1px solid red;
}
#child2 {
width: 100%;
border: 1px solid blue;
}
As you shrink the screen small enough that the table (chld 1) stops shrinking and it forces the parent to overflow and thus show the scrollbar, the second child still retains 100% of the OUTER width of the parent, I would like it to be 100% of the INNER width of the parent so that it is the same width as the first child (table).
I would like to keep the answer as pure of CSS as possible, JavaScript would be fine as long as it doesn't rely on window resize events because the #parent may be shrunk for other reasons (a horizontal sibling grows).
Do you have to keep the long title as one line? if not, you can try this code;
#parent {
margin: 16px;
border: 1px solid black;
box-sizing: border-box;
box-sizing: border-box;
}
#child1 {
width: 100%;
border:1px solid red;
box-sizing: border-box;
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
}
#child2 {
border:1px solid blue;
box-sizing: border-box;
}
<div id='parent'>
<table id='child1'>
<colgroup>
<col width='400px'></col>
</colgroup>
<tbody>
<tr>
<td>Child1withareallyreallylongtitle</td>
</tr>
</tbody>
</table>
<div id='child2'>
<p>Child 2</p>
</div>
</div>
It took me quite a while to understand what you were getting at, but I think I understand now. You just want the child2 div to span the full width of the parent element, when the child1 table causes a horizontal scroll to appear.
This guy explains the problem pretty well. After reading it I can see that what you are trying to achieve isn't possible with the HTML structure you have and without out using JS. He explains that you can do it by applying inline-block to the parent and applying a min-width of 100%, but that only works on the main browser windows horizontal scrolls.
Here is a display table solution if you are happy to change your HTML.
#parent {
overflow-x:auto;
border: 1px solid black;
/* margin for display purposes in full screen snippet viewer */
margin-top:80px;
}
.table {
display:table;
width:100%;
}
.table-row {
display:table-row;
}
.table-cell {
display:table-cell;
}
.child1 {
border: 1px solid red;
}
.child2 {
border: 1px solid blue;
}
<div id="parent">
<div class="table">
<div class="table-row">
<div class="table-cell child1">
I live in Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch.
</div>
</div>
<div class="table-row">
<div class="table-cell child2">
Child 2
</div>
</div>
</div>
</div>
Add box-sizing: border-box; rule to #child2:
body {
padding: 0.1px;
}
#parent {
margin: 16px;
border: 1px solid black;
box-sizing: border-box;
overflow-x: auto;
}
#child1 {
width: 100%;
border: 1px solid red;
}
#child2 {
width: 100%;
border: 1px solid blue;
box-sizing: border-box;
}
<div id='parent'>
<table id='child1'>
<colgroup>
<col width='400px'></col>
</colgroup>
<tbody>
<tr>
<td>Child1withareallyreallylongtitle</td>
</tr>
</tbody>
</table>
<div id='child2'>
<p>Child 2</p>
</div>
</div>
About border-box, see it here: https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing.
border property will increase elements width since it adds to outer space except td elements.
Your child 2 element has border property thats why your getting scroll bar
This stackoverflow link explains it better
Apologies, I misunderstood the question completely.
Are you looking for this output
<div id="top">
<div id='parent'>
<table id='child1'>
<colgroup>
<col width='400px'></col>
</colgroup>
<tr>
<td>Child1withareallyreallylongtitle</td>
</tr>
</table>
<div id='child2'>
<p>Child 2</p>
</div>
</div>
</div>
CSS
#top{
margin: 16px;
border: 1px solid black;
box-sizing: border-box;
overflow-x: auto;
}
#parent {
width : 100%;
display :table;
}
#child1 {
width: 100%;
border: 1px solid red;
}
#child2 {
width: 100%;
border: 1px solid blue;
}

Make divs on same row the same height - Dynamic Content

I am working on an application that generates dynamic content and displays them on floating divs. Each div takes 49% width of the page. The problem I'm running into is that the height of the divs vary depending on the content.
What I'm looking to do is make the divs on the same row the same height. Any suggestions?
.item {
background: #c4c4c4;
width: 49%;
border: 1px solid black;
float: left;
}
<div id="container">
<div class="item">
Test
</div>
<div class="item">
Hello.
Sample <br>
Content <br>
</div>
<div class="item">
Test<br>
Sample Content
</div>
<div class="item">
Test
</div>
</div>
Using CSS3 flexbox to make the #container adapt flexible box layout.
Default value of flex-wrap is nowrap so it aligns in a single row. Use flex-wrap: wrap to create multiple rows based on item's width.
Current browser support for Flexbox is pretty good: Can I use Flexbox?
#container {
display: flex;
flex-wrap: wrap; /* Wrap after the items fill the row */
/* Safari specific rules */
display: -webkit-flex;
-webkit-flex-wrap: wrap;
}
.item {
background: #c4c4c4;
border: 1px solid black;
width: 49%;
}
<div id="container">
<div class="item">
Test
</div>
<div class="item">
Hello. Sample
<br>Content
<br>
</div>
<div class="item">
Test
<br>Sample Content
</div>
<div class="item">
Test
</div>
</div>
Use display: table on the containing div, and display: block on your "table cell" divs.
Another answer involves using the display: table property in CSS. It is similar to table scaffolding, but allows much more flexibility with CSS and has more browser support than flexbox.
HTML:
<div id="container">
<div class="row">
<div class="item">
Test
</div>
<div class="item">
Hello.
Sample <br>
Content <br>
</div>
</div>
<div class="row">
<div class="item">
Test<br>
Sample Content
</div>
<div class="item">
Test
</div>
</div>
</div>
CSS:
#container {
width: 100%;
display: table;
}
.row {
display: table-row;
}
.item {
background: #c4c4c4;
width: 49%;
box-sizing: border-box;
border: 1px solid black;
display: table-cell;
}
Fiddle:
http://jsfiddle.net/q5jyfuy6/
You can add like height:40px; in your .item class to make height of the divs independent of the content.
.item {
background: #c4c4c4;
width: 49%;
border: 1px solid black;
float: left;
height:40px;
}

Categories

Resources