Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active February 12, 2020 06:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmakeig/32f73ea3f08f01eaba96918e6ddf76ef to your computer and use it in GitHub Desktop.
Save jmakeig/32f73ea3f08f01eaba96918e6ddf76ef to your computer and use it in GitHub Desktop.
Display complex tree data structures as nested HTML tables.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Nested Tables</title>
<style>
html {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 14px;
}
body {
padding: 1em;
}
pre {
font-family: monospace;
font-size: 80%;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 0.5em;
border: solid 0.5px #ccc;
text-align: left;
vertical-align: top;
}
th {
position: relative;
padding-right: 1.75em;
background: #efefef;
}
th.string::after,
th.number::after,
th.date::after,
th.object::after {
position: absolute;
top: 1em;
right: 0.75em;
display: inline-block;
width: 1em;
padding: 0.25em;
border-radius: 50%;
color: #ccc;
font-size: 65%;
font-weight: normal;
line-height: 1;
text-align: center;
}
th.string::after {
content: 'S';
background: #3700b3;
}
th.number::after {
color: #333;
content: 'N';
background: #03dac6;
}
th.number.currency::after {
content: '$' !important;
}
th.date::after {
color: #333;
content: 'D';
background: #ffd600;
}
th.object::after {
color: #333;
content: 'O';
background: #0091ea;
}
td.number,
td.date {
text-align: right;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
th,
td.string {
overflow-wrap: break-word;
}
.heading {
display: flex;
flex-flow: row wrap;
}
.heading > * {
/* white-space: nowrap; */
display: inline-block;
flex-basis: 0;
flex: 1;
}
.heading > .label {
margin-right: 0.5em;
order: 1;
flex: 4;
}
.heading > .name {
color: #999;
font-size: 65%;
text-align: right;
text-align: left;
order: 3;
flex-basis: 100%;
}
.heading > .name:before {
content: '(';
}
.heading > .name:after {
content: ')';
}
.heading > .count {
text-align: right;
order: 2;
flex: 1;
}
td.error {
color: rgb(245, 34, 45);
background-color: rgb(255, 241, 240);
}
td.error:before {
position: relative;
top: -1px;
display: inline-block;
width: 6px;
height: 6px;
margin-right: 0.25em;
border-radius: 50%;
content: '';
background-color: rgb(245, 34, 45);
}
table.collapsed tbody {
display: none;
}
table.collapsed thead {
display: flex column;
}
table.collapsed thead th {
display: block;
/* Hide continguous borders */
margin-top: -1px;
}
</style>
</head>
<body>
<h2>Table</h2>
<table>
<thead>
<tr>
<th class="number">
<span class="heading"
><span class="label">Employee Number</span
><span class="name">emp_no</span></span
>
</th>
<th class="string">
<span class="heading"
><span class="label">First Name</span
><span class="name">first_name</span></span
>
</th>
<th class="string">
<span class="heading"
><span class="label">Last Name</span
><span class="name">last_name</span></span
>
</th>
<th class="string">
<span class="heading"
><span class="label">Gender</span
><span class="name">gender</span></span
>
</th>
<th class="date">
<span class="heading"
><span class="label">Hire Date</span
><span class="name">hire_date</span></span
>
</th>
<th class="object" id="work_experience">
<span class="heading"
><span class="label">Work Experience</span
><span class="name">work_experience</span></span
>
</th>
<th class="object" scope="col" id="salary">
<span class="heading" title="Salary history">
<span class="label">Salary History</span>
<span class="name">salary</span>
<span class="count">15</span>
</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="number id">281328</td>
<td class="string">Eishiro</td>
<td class="string">Trachtenberg</td>
<td class="string">M</td>
<td class="date">1988-01-21</td>
<td class="object" headers="work_experience">
<table>
<thead>
<tr>
<th class="number id">
<span class="heading"
><span class="label">Employee Number</span
><span class="name">emp_no</span></span
>
</th>
<th class="string">
<span class="heading"
><span class="label">Department Number</span
><span class="name">dept_no</span></span
>
</th>
<th class="string">
<span class="heading"
><span class="label">Department Name</span
><span class="name">dept_name</span></span
>
</th>
<th class="object array" id="work_experience.titles">
<span
class="heading"
title="The jobs an employee has held. Ordered in reverse chronological order."
>
<span class="label">Job Titles</span>
<span class="name">titles</span>
<span class="count">2</span>
</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="number id">281328</td>
<td class="string">d008</td>
<td class="string">Research</td>
<td class="object array" headers="work_experience.titles">
<table>
<thead>
<tr>
<th class="string">
<span class="heading"
><span class="label">Job Title</span
><span class="name">title</span></span
>
</th>
<th class="date">
<span class="heading"
><span class="label">From</span
><span class="name">from_date</span></span
>
</th>
<th class="date">
<span class="heading"
><span class="label">To</span
><span class="name">to_date</span></span
>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="string">Staff</td>
<td class="date">1988-01-21</td>
<td class="date">1996-01-21</td>
</tr>
<tr>
<td class="string">Senior Staff</td>
<td class="date">1996-01-21</td>
<td class="date">9999-01-01</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
<td class="object" headers="salary">
<table>
<thead>
<tr>
<th class="number currency">
<span class="heading"
><span class="label">Salary</span
><span class="name">salary</span></span
>
</th>
<th class="date">
<span class="heading"
><span class="label">From</span
><span class="name">from</span></span
>
</th>
<th class="date">
<span class="heading"
><span class="label">To</span
><span class="name">to_date</span></span
>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="number currency">56926</td>
<td class="date">1995-01-19</td>
<td class="date">1996-01-19</td>
</tr>
<tr>
<td class="number currency">51184</td>
<td class="date error" title="Error: Invalid date">
1989-XX-XX
</td>
<td class="date">1990-01-20</td>
</tr>
<tr>
<td class="number currency">60014</td>
<td class="date">1999-01-18</td>
<td class="date">2000-01-18</td>
</tr>
<tr>
<td class="number currency">47619</td>
<td class="date">1988-01-21</td>
<td class="date">1989-01-20</td>
</tr>
<tr>
<td class="number currency">58153</td>
<td class="date">1996-01-19</td>
<td class="date">1997-01-18</td>
</tr>
<tr>
<td class="number currency">51837</td>
<td class="date">1990-01-20</td>
<td class="date">1991-01-20</td>
</tr>
<tr>
<td class="number currency">56264</td>
<td class="date">1993-01-19</td>
<td class="date">1994-01-19</td>
</tr>
<tr>
<td class="number currency">59706</td>
<td class="date">1998-01-18</td>
<td class="date">1999-01-18</td>
</tr>
<tr>
<td class="number currency">62624</td>
<td class="date">2001-01-17</td>
<td class="date">2002-01-17</td>
</tr>
<tr>
<td class="number currency">63223</td>
<td class="date">2002-01-17</td>
<td class="date">9999-01-01</td>
</tr>
<tr>
<td class="number currency">57000</td>
<td class="date">1994-01-19</td>
<td class="date">1995-01-19</td>
</tr>
<tr>
<td class="number currency">55221</td>
<td class="date">1991-01-20</td>
<td class="date">1992-01-20</td>
</tr>
<tr>
<td class="number currency">54926</td>
<td class="date">1992-01-20</td>
<td class="date">1993-01-19</td>
</tr>
<tr>
<td class="number currency">62816</td>
<td class="date">2000-01-18</td>
<td class="date">2001-01-17</td>
</tr>
<tr>
<td class="number currency">58096</td>
<td class="date">1997-01-18</td>
<td class="date">1998-01-18</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<h2>Data</h2>
<pre>
{
"emp_no": 281328,
"first_name": "Eishiro",
"last_name": "Trachtenberg",
"gender": "M",
"hire_date": "1988-01-21",
"work_experience": {
"emp_no": 281328,
"dept_no": "d008",
"dept_name": "Research",
"titles": [
{
"title": "Staff",
"from_date": "1988-01-21",
"to_date": "1996-01-21"
},
{
"title": "Senior Staff",
"from_date": "1996-01-21",
"to_date": "9999-01-01"
}
]
},
"salary": [
{
"salary": "56926",
"from_date": "1995-01-19",
"to_date": "1996-01-19"
},
{
"salary": "51184",
"from_date": "1989-01-20",
"to_date": "1990-01-20"
},
{
"salary": "60014",
"from_date": "1999-01-18",
"to_date": "2000-01-18"
},
{
"salary": "47619",
"from_date": "1988-01-21",
"to_date": "1989-01-20"
},
{
"salary": "58153",
"from_date": "1996-01-19",
"to_date": "1997-01-18"
},
{
"salary": "51837",
"from_date": "1990-01-20",
"to_date": "1991-01-20"
},
{
"salary": "56264",
"from_date": "1993-01-19",
"to_date": "1994-01-19"
},
{
"salary": "59706",
"from_date": "1998-01-18",
"to_date": "1999-01-18"
},
{
"salary": "62624",
"from_date": "2001-01-17",
"to_date": "2002-01-17"
},
{
"salary": "63223",
"from_date": "2002-01-17",
"to_date": "9999-01-01"
},
{
"salary": "57000",
"from_date": "1994-01-19",
"to_date": "1995-01-19"
},
{
"salary": "55221",
"from_date": "1991-01-20",
"to_date": "1992-01-20"
},
{
"salary": "54926",
"from_date": "1992-01-20",
"to_date": "1993-01-19"
},
{
"salary": "62816",
"from_date": "2000-01-18",
"to_date": "2001-01-17"
},
{
"salary": "58096",
"from_date": "1997-01-18",
"to_date": "1998-01-18"
}
]
}
</pre
>
</body>
<script type="text/javascript">
const usd = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0
});
for (const el of document.querySelectorAll('td.number.currency')) {
el.dataset.value = el.textContent.trim();
el.textContent = usd.format(el.textContent);
}
// FIXME: This is really ugly and needs to be factored into proper components.
const headers = document.querySelectorAll('th.object');
for (const header of headers) {
const id = header.id;
toggleFor(id, true);
header.addEventListener('click', event => {
toggleFor(id);
});
}
function toggleFor(id, toggle) {
const table = document.querySelector(`td[headers="${id}"] > table`);
if (table) {
if (undefined === toggle) {
table.classList.toggle('collapsed');
} else if (toggle) {
table.classList.add('collapsed');
} else {
table.classList.remove('collapsed');
}
}
}
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment