I would like to add a new sublevel to my secondary row on a table that I have made with the help of Datatables, this time I want to generate a new child to my secondary row, I enclose an example with which I want to explain and that I found from Datatables which is a what I'm getting at, I'm just a bit confused by the syntax used here versus what I'm using in my current code.
I attach the Javascript code with which I build my Datatable with a single secondary row:
/* Formatting function for row details - modify as you need */
function format1(d) {
// `d` is the original data object for the row
console.log(d);
let tabla = `<table cellpadding="5" cellspacing="0" style="border-collapse: separate; border-spacing: 40px 5px;">
<thead>
<tr>
<th>
Fecha
</th>
<th>
No. Consecutivo
</th>
</tr>
</thead>
<tbody>`;
d.Consecutivo.forEach(f => {
tabla += `<tr>
<td>${f.Fecha}</td>
<td>${f.Consecutivo}</td>
</tr>`;
});
tabla += '</tbody></table>';
return tabla;
}
$(document).ready(function () {
$('#example').dataTable( {
responsive : true,
ajax : {
"type": 'POST',
"url" : './test.php',
"dataType": 'JSON',
"cache": false,
"data": {
'param' : 1,
},
},
language : {
"lengthMenu": "Mostrar _MENU_ registros",
"zeroRecords": "No se encontró nada",
"info": "Mostrando del _START_ al _END_ de un total de _TOTAL_",
"infoEmpty": "No hay registros",
"emptyTable": "No hay datos para mostrar",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"infoFiltered": "(filtrado de un total de _MAX_ registros)",
"paginate": {
"first": "Primera",
"last": "Última",
"next": "Siguiente",
"previous": "Anterior"
}
},
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ "data" : "OrdenCompra" },
{ "data" : "FechaOrdenCompra" },
{ "data" : "Monto"},
{ "data" : "TipoMoneda" },
{ "data" : "Estatus" },
],
order : [[1, 'desc']],
} );
// Add event listener for opening and closing details
$('#example tbody').on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = $('#example').DataTable().row(tr);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
}
else {
// Open this row
row.child(format1(row.data())).show();
tr.addClass('shown');
}
});
});
I would appreciate if someone can give me some guidance on this issue.
Update 1:
Possibly the question is a bit confusing but with this I explain a little more in detail, the header where the Purchase Order is found is the parent row, the No. Consecutive header is a child row of the Purchase Order parent row and the Date header is a child row of Consecutive No.
Update 2:
Between searching and searching for documentation I found another example , but it still doesn't give me a clearer idea, I don't know if it is necessary to modify the code that I am using and adapt it to what I have found in the examples or with the code that I am using I can do what I have planned.
Update 3:
Trying to contribute one of the answers, I get the following error:
Uncaught ReferenceError: d is not defined
Here below the code I use:
/* Formatting function for row details - modify as you need */
function format1(d) {
// `d` is the original data object for the row
console.log(d);
let tabla = `<table id="tb-${d.OrdenCompra}" cellpadding="5" cellspacing="0" style="border-collapse: separate; border-spacing: 40px 5px;">
<thead>
<tr>
<th>
</th>
<th>
Fecha
</th>
<th>
No. Consecutivo
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>`;
return $(tabla).toArray();
}
$(document).ready(function () {
$('#example').dataTable( {
responsive : true,
ajax : {
"type": 'POST',
"url" : './test.php',
"dataType": 'JSON',
"cache": false,
"data": {
'param' : 1,
},
},
language : {
"lengthMenu": "Mostrar _MENU_ registros",
"zeroRecords": "No se encontró nada",
"info": "Mostrando del _START_ al _END_ de un total de _TOTAL_",
"infoEmpty": "No hay registros",
"emptyTable": "No hay datos para mostrar",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"infoFiltered": "(filtrado de un total de _MAX_ registros)",
"paginate": {
"first": "Primera",
"last": "Última",
"next": "Siguiente",
"previous": "Anterior"
}
},
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ "data" : "OrdenCompra" },
{ "data" : "FechaOrdenCompra" },
{ "data" : "Monto"},
{ "data" : "TipoMoneda" },
{ "data" : "Estatus" },
],
order : [[1, 'desc']],
} );
// Add event listener for opening and closing details
$('#example tbody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = $('#example').DataTable().row(tr);
//Se agrega fila de primer nivel, segun el ejemplo es No. Consecutivo
let rowData = NoConsecutivo.data;
let tbId = `#tb-${d.OrdenCompra}`;
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
$(tbId).DataTable().destroy();
}
else {
// Open this row
row.child(format1(row.data())).show();
$(tbId).DataTable({
//Se agrega la fila de segundo nivel, segun el ejemplo es la Fecha
rowData.Fecha,
});
//No es necesario agregar una fila de tercer nivel por eso se comenta
/* $(tbId).on('click', 'td.details-control', function(){
});*/
tr.addClass('shown');
}
});
Update 4:
Improving the code a little more, I have reached this point where I can locate the button to expand the second child row, but when clicking on it, it does not expand. I am attaching an example and sharing the slightly improved code:
/* Formatting function for row details - modify as you need */
function format1(d) {
// `d` is the original data object for the row
console.log(d);
let tabla = `<table id="tb-${d.OrdenCompra}" cellpadding="5" cellspacing="0" style="border-collapse: separate; border-spacing: 40px 5px;">
<thead>
<tr>
<th>
No. Consecutivo
</th>
<th>
Fecha
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>`;
return $(tabla).toArray();
}
$(document).ready(function () {
$('#example').dataTable( {
responsive : true,
ajax : {
"type": 'POST',
"url" : './test.php',
"dataType": 'JSON',
"cache": false,
"data": {
'param' : 1,
},
},
language : {
"lengthMenu": "Mostrar _MENU_ registros",
"zeroRecords": "No se encontró nada",
"info": "Mostrando del _START_ al _END_ de un total de _TOTAL_",
"infoEmpty": "No hay registros",
"emptyTable": "No hay datos para mostrar",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"infoFiltered": "(filtrado de un total de _MAX_ registros)",
"paginate": {
"first": "Primera",
"last": "Última",
"next": "Siguiente",
"previous": "Anterior"
}
},
//"array.json",
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ "data" : "OrdenCompra" },
{ "data" : "FechaOrdenCompra" },
{ "data" : "Monto"},
{ "data" : "TipoMoneda" },
{ "data" : "Estatus" },
],
order : [[1, 'desc']],
} );
// Add event listener for opening and closing details
$('#example tbody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = $('#example').DataTable().row(tr);
let rowData = row.data();
let tbId = `#tb-${rowData.OrdenCompra}`;
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
$(tbId).DataTable().destroy();
}
else {
// Open this row
row.child(format1(rowData)).show();
$(tbId).DataTable({
data: rowData.Consecutivo,
"searching": false,
"bPaginate": false,
"info" : false,
columns: [
{
"className": 'details-control1',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ data: 'Consecutivo' },
{ data: 'Fecha' },
],
});
$(tbId).on('click', 'td.details-control', function(){
var tr = $(this).closest('tr');
var row = $('#example').DataTable().row(tr);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
}
else {
// Open this row
row.child(format1(row.data())).show();
tr.addClass('shown');
}
});
tr.addClass('shown');
}
});
I still cannot figure out why the second daughter row is not located below the first daughter row.
Update 5:
Testing the updated answer now I am running into this error:
Uncaught ReferenceError: Cannot access 'rowData' before initialization
The code I'm using now based on the updated answer is as follows:
/* Formatting function for row details - modify as you need */
function format2(d) {
// `d` is the original data object for the row
console.log(d);
let tabla = `<table id="tb-${d.Consecutivo}" cellpadding="5" cellspacing="0" style="border-collapse: separate; border-spacing: 40px 5px;">
<thead>
<tr>
<th>
Fecha
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>`;
return $(tabla).toArray();
}
$(document).ready(function () {
$('#example').dataTable( {
responsive : true,
// "processing": true,
// "serverSide": true,
ajax : {
"type": 'POST',
"url" : './test.php',
"dataType": 'JSON',
"cache": false,
"data": {
'param' : 1,
},
},
language : {
"lengthMenu": "Mostrar _MENU_ registros",
"zeroRecords": "No se encontró nada",
"info": "Mostrando del _START_ al _END_ de un total de _TOTAL_",
"infoEmpty": "No hay registros",
"emptyTable": "No hay datos para mostrar",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"infoFiltered": "(filtrado de un total de _MAX_ registros)",
"paginate": {
"first": "Primera",
"last": "Última",
"next": "Siguiente",
"previous": "Anterior"
}
},
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ "data" : "OrdenCompra" },
{ "data" : "FechaOrdenCompra" },
{ "data" : "Monto"},
{ "data" : "TipoMoneda" },
{ "data" : "Estatus" },
],
order : [[1, 'desc']],
} );
// Add event listener for opening and closing details
$('#example tbody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = $('#example').DataTable().row(tr);
let rowData = row.data();
let tbId = `#tb-${rowData.OrdenCompra}`;
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
$(tbId).DataTable().destroy();
}
else {
// Open this row
row.child(format1(rowData)).show();
let rowData = row.data();
$(tbId).DataTable({
data: rowData.Consecutivo,
"searching": false,
"bPaginate": false,
"info" : false,
columns: [
{
"className": 'details-control1',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ data: 'Consecutivo' },
]
});
// Add event listener for opening and closing details
$('[id^="tb-"]').find('tbody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
// No estoy seguro de esta parte, pero deberías obtener la tabla correcta
let parentTable = $(tr).closest('table');
// Desde la tabla se obtienen los datos para crear la hija
let row = $(parentTable).DataTable().row(tr);
// ID de la nueva tabla hija
let tbId = `#tbc-${rowData.Consecutivo}`;
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
// Destruir tabla hija (no entiendo porqué)
$(tbId).DataTable().destroy();
}
else {
// Open this row
row.child(format1(rowData)).show();
// Vas a necesitar los datos de la fila
// Se supone que solo tienes Consecutivo y Fecha
let rowData = row.data();
// Activar la tabla hija como DataTable
$(tbId).DataTable({
// Agregar origen de datos como arreglo
data: [rowData],
// Especificar columnas
columns: [
{ data: 'Fecha' },
]
});
}
});
tr.addClass('shown');
}
});
});
Update 6:
After several days without sleep I provide this small reproducible minimum example with the aim of solving this question and validating it
var purchase_data = [
{
PurchaseOrder: 789,
DatePurchaseOrder: "27/04/2021",
Total: "$100",
Currency: "USD",
Status: "Delivered",
Consecutivo: 999,
Date: "26/04/2021"
}, {
PurchaseOrder: 790,
DatePurchaseOrder: "27/04/2021",
Total: "$100",
Currency: "USD",
Status: "In Wait",
Consecutivo: 1000,
Date: "26/04/2021"
}, {
PurchaseOrder: 791,
DatePurchaseOrder: "28/04/2021",
Total: "$100",
Currency: "USD",
Status: "Delivered",
Consecutivo: 1001,
Date: "27/04/2021"
}, {
PurchaseOrder: 792,
DatePurchaseOrder: "28/04/2021",
Total: "$100",
Currency: "USD",
Status: "Delivered",
Consecutivo: 1002,
Date: "27/04/2021"
},
];
/* Formatting function for row details - modify as you need */
function format1(d) {
// `d` is the original data object for the row
console.log(d);
let tabla = `<table id="tb-${d.PurchaseOrder}" cellpadding="5" cellspacing="0" style="border-collapse: separate; border-spacing: 40px 5px;">
<thead>
<tr>
<th></th>
<th>
No. Consecutivo
</th>
<th>
Fecha
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>`;
return $(tabla).toArray();
}
$(document).ready(function () {
$('#example').DataTable({
data: purchase_data,
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ "data" : "PurchaseOrder" },
{ "data" : "DatePurchaseOrder" },
{ "data" : "Total"},
{ "data" : "Currency" },
{ "data" : "Status" }
],
order : [[1, 'desc']]
});
// Add event listener for opening and closing details
$('#example tbody').on('click', 'td.details-control', function () {
let tr = $(this).closest('tr');
let row = $('#example').DataTable().row(tr);
let rowData = row.data();
let tbId = `#tb-${rowData.PurchaseOrder}`;
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
$(tbId).DataTable().destroy();
}
else {
// Open this row
row.child(format1(rowData)).show();
$(tbId).DataTable({
data: [rowData],
"searching": false,
"bPaginate": false,
"info" : false,
columns: [
{
"className": 'details-control',
"orderable": false,
"data": null,
"defaultContent": ''
},
{ data: 'Consecutivo' },
{ data: 'Date' }
]
});
tr.addClass('shown');
}
});
});
.content {
padding: 15px;
}
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;
}
table.dataTable td table.dataTable,
table.dataTable td table.dataTable * {
border: none;
}
<link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div id="box-body" style="padding: 0px 10px 10px 10px;">
<table id="example" class="table table-bordered table-hover">
<thead>
<tr>
<th></th>
<th>OrdenCompra</th>
<th>FechaOrdenCompra</th>
<th>Monto</th>
<th>Moneda</th>
<th>Estatus</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</section>
Please remember that Date must be the daughter of Consecutive No.
Update 7:
Sigo sin alguna solución despues de probar la respuesta, me veo en la necesidad de agregar el codigo en como esta construido desde el back con PHP
$query = array();
include './db/conectar.php';
$USER = $_POST['proveedor'];
$sql = "{call SpTest()}";
$params = array($USER);
$stmt = sqlsrv_query($conn, $sql, $params);
if ( $stmt === false) {
die( print_r( sqlsrv_errors(), true) );
}
//Se inicializa el arreglo antes del ciclo
$query = [];
//Se necesita numero de orden de compra para agrupar
$ultimaOrden = '';
while( $row = sqlsrv_fetch_array($stmt) ) {
if(strcmp($ultimaOrden, $row['PurchaseOrder']) != 0) {
$query[] = [
"PurchaseOrder" => $row['PurchaseOrder'],
"DatePurchaseOrder" => $row['DatePurchaseOrder']->format('d/m/Y'), //Fecha de orden de compra
"Total" => $row['Total'], //Monto
"Currency" => $row['Currency'], //Moneda
"Status" => $row['Status'], //Estatus
"Consecutivo" => []
];
$ultimaOrden = $row['PurchaseOder'];
}
$indice = count($query) - 1;
if($indice < 0) {
die('Error: No se agregó orden de compra.');
}
$query[$indice]["Consecutivo"][] = [
"Date" => $row['Date'] != null ? $row['FechaFactura']->format('d/m/Y'):"",
"Consecutivo" => utf8_encode ($row['Consecutivo']),
];
}
sqlsrv_free_stmt( $stmt);
sqlsrv_close($conn);
$json = [
"success"=> count($query) > 0 ? true : false,
"data"=>$query
];
echo json_encode($json);
De acuerdo al enlace que proporcionaste, hay varios pasos a realizar, el primero es que en la función
format1()
solo creas la tabla y la devuelves como arreglo de jQuery (no me queda claro porqué):Cuando manipulas las "filas secundarias" (tabla hija), debes llenar y activar como
DataTable
al mostrar y destruir al ocultar:Desconozco si esto va a funcionar, pero, por la lógica mostrada en el primer enlace de ejemplo, se supone que deberías seguir estos pasos:
Para crear otra tabla hija, primero necesitas otra función para representar los datos:
Escucha los clics en el cuerpo de la tabla hija:
With the edit you made to work with a verifiable example, it's easier to get to the result: