<meta name="description" content="DataTable with four levels of child-row detail using Nested Tables" />
<!-- Bootstrap CSS CDN -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
<!-- DataTables Custom CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.2.3/css/responsive.dataTables.min.css" />
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/1.5.4/css/buttons.dataTables.min.css" />
<div class="row row-padding">
<div class="col-12">
<div class="card">
<div class="card-body">
<table id="example" class="display compact nowrap w-100">
<thead>
<tr>
<th></th>
<th>Client | Matter | Invoice</th>
<th style="width:69px">WIP</th>
<th style="width:69px">AR</th>
<th style="width:69px">0-30</th>
<th style="width:69px">31-60</th>
<th style="width:69px">61-90</th>
<th style="width:69px">91-120</th>
<th style="width:69px">121+</th>
<th style="width:69px">Susp</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<!-- jQuery CDN -->
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<!-- Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
<!-- DataTables CDN -->
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.2.3/js/dataTables.responsive.min.js"></script>
@import "https://fonts.googleapis.com/css?family=Muli:300,400,500,600,700";
table.display {
font-family: 'Muli'
}
table.dataTable.compact tbody th, table.dataTable.compact tbody td, table.dataTable.compact tfoot td {
/*padding: 0px 17px 0px 4px;*/
padding: 0px 0px 0px 0px;
}
td.details-control {
background: url(https://www.datatables.net/examples/resources/details_open.png) no-repeat center center;
cursor: pointer;
width: 30px;
transition: .5s;
}
tr.shown td.details-control {
background: url(https://www.datatables.net/examples/resources/details_close.png) no-repeat center center;
width: 30px;
transition: .5s;
}
td.details-control1 {
background: url(https://www.datatables.net/examples/resources/details_open.png) no-repeat center center;
cursor: pointer;
width: 40px;
transition: .5s;
}
tr.shown td.details-control1 {
background: url(https://www.datatables.net/examples/resources/details_close.png) no-repeat center center;
width: 40px;
transition: .5s;
}
td.details-control2 {
background: url(https://www.datatables.net/examples/resources/details_open.png) no-repeat center center;
cursor: pointer;
width: 50px;
transition: .5s;
}
tr.shown td.details-control2 {
background: url(https://www.datatables.net/examples/resources/details_close.png) no-repeat center center;
width: 50px;
transition: .5s;
}
.fee-col {
text-align: right;
}
.label-col {
text-align: left;
}
tr.shown td {
background-color: lightgrey !important;
transition: .5s;
font-weight: 800
}
td.invoice-date {
background-color: rgba(237, 205, 255, .2);
}
td.invoice-author {
background-color: rgba(237, 205, 255, .2);
}
td.invoice-notes {
background-color: rgba(237, 205, 255, .2);
}
var childEditors = {}; // Globally track created chid editors
var childTable;
var childTable2;
$(document).ready(function () {
// Return table with id generated from row's name field
function format(rowData) {
var childTable = '<table id="cl' + rowData.clientID + '" class="display compact nowrap w-100" width="100%">' +
'<thead style="display:none"></thead >' +
'</table>';
return $(childTable).toArray();
}
function format2(rowData) {
var childTable = '<table id="mt' + rowData.matterID + '" class="display compact nowrap w-100" width="100%">' +
'<thead style="display:none"></thead >' +
'</table>';
return $(childTable).toArray();
}
function format3(rowData) {
var childTable = '<table id="in' + rowData.invoice + '" class="display wrap w-100 cell-border" width="100%">' +
'<thead style="display:none"></thead >' +
'</table>';
return $(childTable).toArray();
}
// Main table
var table = $('#example').DataTable({
dom: 't',
ajax: {
url: 'https://output.jsbin.com/zisabof.json',
dataSrc: 'data'
},
pageLength: 20,
columns: [
{
className: 'details-control',
orderable: false,
data: null,
defaultContent: ''
},
{
data: "clientName",
"render": function (data, type, row) {
data = '<a href="/client/detail/' + row.clientID + '?name=' + row.clientName + '">' + data + '</a>';
return data;
}
},
{ data: "wip" },
{ data: "ar" },
{ data: "ar0_30" },
{ data: "ar31_60" },
{ data: "ar61_90" },
{ data: "ar91_120" },
{ data: "ar121_" },
{ data: "suspense" }
],
columnDefs: [
{
targets: [2, 3, 4, 5, 6, 7, 8, 9],
className: "fee-col"
},
{
targets: [1],
className: "label-col"
}
],
order: [[1, 'asc']],
});
// Add event listener for opening and closing first level childdetails
$('#example tbody').on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = table.row(tr);
var rowData = row.data();
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
// Destroy the Child Datatable
$('#cl' + rowData.clientID).DataTable().destroy();
}
else {
// Open this row
row.child(format(rowData)).show();
var id = rowData.clientID;
childTable = $('#cl' + id).DataTable({
dom: "t",
ajax: {
url: 'https://output.jsbin.com/dutelom.json',
dataSrc: 'data'
},
columns: [
{
className: 'details-control1',
orderable: false,
data: null,
defaultContent: ''
},
{
data: "matterName",
render: function (data, type, row) {
data = '<a href="/client/detail/' + row.matterID.substring(1, 6) + '?name=[Client Name]">' + data + '</a>';
return data;
}
},
{ data: "wip" },
{ data: "ar" },
{ data: "ar0_30" },
{ data: "ar31_60" },
{ data: "ar61_90" },
{ data: "ar91_120" },
{ data: "ar121_" },
{ data: "suspense" }
],
columnDefs: [
{
targets: [2, 3, 4, 5, 6, 7, 8, 9],
className: "fee-col"
},
{
targets: [1],
className: "label-col"
}
],
select: false,
});
tr.addClass('shown');
}
});
// Add event listener for opening and closing second level childdetails
$('tbody').on('click', 'td.details-control1', function () {
var tr = $(this).closest('tr');
var row = childTable.row(tr);
var rowData = row.data();
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
// Destroy the Child Datatable
$('#mt' + rowData.matterID).DataTable().destroy();
}
else {
// Open this row
row.child(format2(rowData)).show();
var id = rowData.matterID;
childTable2 = $('#mt' + id).DataTable({
dom: "t",
ajax: {
url: 'https://output.jsbin.com/zohiroz.json',
dataSrc: 'data'
},
columns: [
{
className: 'details-control2',
orderable: false,
data: null,
defaultContent: ''
},
{
data: "invoice",
"render": function (data, type, row) {
data = '<a href="#">' + data + '</a>';
return data;
}
},
{ data: "wip" },
{ data: "ar" },
{ data: "ar0_30" },
{ data: "ar31_60" },
{ data: "ar61_90" },
{ data: "ar91_120" },
{ data: "ar121_" },
{ data: "suspense" }
],
columnDefs: [
{
targets: [2, 3, 4, 5, 6, 7, 8, 9],
className: "fee-col"
},
{
targets: [1],
className: "label-col"
}
],
select: false,
});
tr.addClass('shown');
}
});
// Add event listener for opening and closing third level childdetails
$('tbody').on('click', 'td.details-control2', function () {
var tr = $(this).closest('tr');
var row = childTable2.row(tr);
var rowData = row.data();
console.log(rowData.invoice);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
// Destroy the Child Datatable
$('#in' + rowData.invoice).DataTable().destroy();
}
else {
// Open this row
row.child(format3(rowData)).show();
var id = rowData.invoice;
childTable3 = $('#in' + id).DataTable({
dom: "t",
ajax: {
url: 'https://output.jsbin.com/buniye.json',
dataSrc: 'data'
},
columns: [
{
className: 'details-control3',
orderable: false,
data: null,
defaultContent: ''
},
{ data: "date" },
{ data: "author" },
{ data: "note" }
],
columnDefs: [
{
targets: [1],
className: "invoice-date pl-5"
},
{
targets: [2],
className: "invoice-author px-2"
},
{
targets: [3],
className: "invoice-notes px-2"
}
],
select: false,
});
tr.addClass('shown');
}
});
});
You can jump to the latest bin by adding /latest
to your URL
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + [ | Indents selected lines |
ctrl + ] | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Clone Bin |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |