Datatables: How can I keep child rows opened after the Ajax reload

datatables child rows
jquery datatable nested table expand/collapse

I'm using an Ajax source to generate the table. This one is refreshed every 5000 ms and when a child row is opened it's then closed by the table's redraw. How can I keep these ones opened?

My code:

    /* Formatting function for row details - modify as you need */
    function format ( d ) {
        // `d` is the original data object for the row
        return '<p>Text in child row</p>';
    }

    $(document).ready(function() {
        $('#table').DataTable( {
        ...
        } );

    var tr;
    var row;

    $('#table tbody').on('click', 'td.details-control', function () {

        if (tr !== null && tr !== undefined && row !== null && tr !== undefined) {
            row.child.hide();
            tr.removeClass('shown');            
        }

        tr = $(this).closest('tr');
        row = table.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( format(row.data()) ).show();
            tr.addClass('shown');
        }

    } );

    $.fn.dataTable.ext.errMode = 'none';    
    var table = $('#table').DataTable();
    setInterval( function () {
        table.ajax.reload( function () {
            if ( row.child.isShown() ) {
                // This row is already open - close it
                row.child.hide();
                tr.removeClass('shown');
            }
            else {          
                if (tr.hasClass('shown')) {
                    // Open this row
                    row.child( format(row.data()) ).show();
                    tr.addClass('shown');                       
                }
            }
        } );
    }, 5000 );  

    $('table td .details-control').html('<button><i class="fa fa-plus"></i></button>');

} );

See Child rows example and ajax.reload() method for reference.

  • After some research I've seen that people suggest using cookies in jQuery

As i have understood you are clearing previous table with new table created by data coming from ajax. You will have to save state of opened rows and whenever you are done with refreshing table expand rows with your saved state.

How can I keep child rows opened after the Ajax reload, I'm using an Ajax source to generate the table. This one is refreshed every 5000 ms and when a child row is opened it's then closed by the  I'm confused about what you are doing. Maybe you can post a link to your page or a test case replicating the issue. You are using this: table.row(row).child(Format(ID)).show(); - ID coming from post call

To give an answer to the initial question (how to keep child rows open on DataTable AJAX reload), see the following implementation.

I use cookies to keep the children rows open and I am using the plugin js-cookie found here.

It is better to have a unique identifier as a column of your table so that the re-opened rows are the right one.

$(function(){

  var dt = $('#my_table').DataTable(...);
  var reloadInterval = 10000; // milliseconds

  // Add extra-info row
  dt_add_details('my_table', 'details-control', formatCallback, 'id');
  var reloadCallback = function(json){
    dt_reopen_rows('my_table', formatCallback, 'id')
  };

  // Reload AJAX source every X seconds
  setInterval(function(){
    dt.ajax.reload(reloadCallback, false);
  }, reloadInterval)
});

/**
 * Format child row data.
 */
function formatCallback(d){
  ...
}

/**
 * Show / Hide extra-info when clicking on the column identified by className.
 * @param {String} selector - The HTML selector for the table.
 * @param {String} className - The column class name that holds the extra-info.
 * @param {Function} formatCallback - Function used to format the data of the
 * child row.
 * @param {String} cookieDataIndex - The data index to keep in cookie.
 */

function dt_add_details(selector, className, formatCallback, cookieDataIndex){
  $(selector + ' tbody').on('click', 'td.' + className, function () {
    var ckey = 'openRows_' + selector;
    var openRows = Cookies.getJSON(ckey);

    // Create cookie if never created
    if (typeof openRows == 'undefined'){
      Cookies.set(ckey, [], {'path': ''});
      var openRows = Cookies.getJSON(ckey);
    };

    // Get current info
    var tr = $(this).closest('tr');
    var row = $(selector).DataTable().row(tr);
    var id = row.data()[cookieDataIndex];

    if (row.child.isShown()){
        // This row is already open - close it
        row.child.hide();
        tr.removeClass('shown');

        // Remove opened row from cookie
        var idx = openRows.indexOf(id);
        openRows.splice(idx, 1);
        Cookies.set(ckey, openRows, {path: ''});
    }
    else{
        // Open this row
        row.child(formatCallback(row.data())).show();
        tr.addClass('shown');

        // Add row 'id' field to cookie
        if (openRows.indexOf(id) < 0){
          openRows.push(id);
        }
        Cookies.set(ckey, openRows, {path: ''});
    }
    // console.log("Opened rows: " + Cookies.getJSON('openRows_' + selector))
  });
}

/**
 * Show / Hide extra-info when clicking on the column identified by className.
 * @param {String} selector - The HTML selector for the table.
 * @param {Function} formatCallback - Function used to format the data of the
 * the child row.
 * @param {String} cookieDataIndex - The data index to keep in cookie.
 */
function dt_reopen_rows(selector, formatCallback, cookieDataIndex) {
    var ckey = 'openRows_' + selector;
    var openRows = Cookies.getJSON(ckey);
    if (!openRows)
        return;
    var table = $(selector).DataTable(); // existing DataTable
    $(table.rows().nodes()).each(function (idx, tr) {
        var row = table.row(tr);
        var id = row.data()[cookieDataIndex]
        if (openRows.indexOf(id) >= 0) {
            // console.log("Id " + id + " found in previously opened row. Re-opening.")
            $(tr).addClass("shown");
            row.child(formatCallback(row.data())).show();
        }
    });
}

Ajax Reload keep the Child rows open but also writes data to , Ajax Reload keep the Child rows open but also writes data to another child, it shows in both now even after refresh it shows it been added in one, how can i fix that The Issue is happening on table redraw of Datatables  As you see, firstly the containers table is reloaded, and once the data is available, every row is traversed to check whether it was expanded. If that's the case, then a new DataTable subtable is created and added as a row.child. Table and opened subtables are correctly reloaded but I'm unable to go back to the previous scrolling position.

Using this solution, every row in your table should have a row id. For more details see: https://datatables.net/reference/option/rowId

HTML

<script src="http://code.jquery.com/jquery-latest.min.js"
        type="text/javascript"></script>
<link rel="stylesheet" type="text/css"
      href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>

table id="example" class="display" style="width:100%">
    <thead>
    <tr>
        <th></th>
        <th>Name</th>
        <th>Position</th>
        <th>Office</th>
        <th>Salary</th>
    </tr>
    </thead>
    <tfoot>
    <tr>
        <th></th>
        <th>Name</th>
        <th>Position</th>
        <th>Office</th>
        <th>Salary</th>
    </tr>
    </tfoot>
</table>

Javascript

   /* Function to create a new row, fell free to render your code here */
    function format(d) {
        // `d` is the original data object for the row
        return '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">' +
            '<tr>' +
            '<td>Full name:</td>' +
            '<td>' + d.name + '</td>' +
            '</tr>' +
            '<tr>' +
            '<td>Extension number:</td>' +
            '<td>' + d.extn + '</td>' +
            '</tr>' +
            '<tr>' +
            '<td>Extra info:</td>' +
            '<td>And any further details here (images etc)...</td>' +
            '</tr>' +
            '</table>';
    }

    function onClickEventListener() {
        var tr = $(this).closest('tr');
        var row = table.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(format(row.data())).show();
            tr.addClass('shown');
        }

        let currentRowID = "#" + ($(this).closest('tr').attr('id'));
        if ($.inArray(currentRowID, rowIds) !== -1) {
            //Row is closed, remove row ID from rowIDs array
            var index = rowIds.indexOf(currentRowID);
            if (index !== -1) rowIds.splice(index, 1);
            rowIds.filter(function (val) {
                return val
            });
        } else {
            //Row is opened, add row ID to rowIDs array
            rowIds.push(currentRowID);
        }
    }

    $(document).ready(function () {
        let rowIds = [];
        var table = $('#example').DataTable({
            "ajax": "{{ path('your_data_source') }}",
            "columns": [
                {
                    "className": 'details-control',
                    "orderable": false,
                    "data": null,
                    "defaultContent": ''
                },
                {"data": "name"},
                {"data": "position"},
                {"data": "office"},
                {"data": "salary"}
            ],
            "order": [[1, 'asc']]
        });

        // Add event listener for opening and closing the row
        $('#example tbody').on('click', 'td.details-control', onClickEventListener);

        //set interval to update datatable
        setInterval(function () {
            table.ajax.reload(function () {
                //Iterate through all the open rows and open them again   <--Value is set in the onClickEventListener function
                table.rows(rowIds).every(function (row, index, array) {
                    table.row(row).child(format(this.data())).show();
                    this.nodes().to$().addClass('shown');
                    //Add a minus icon for the open row
                    this.nodes().to$().children('td:first').html('<img style="max-width: 30px;; max-height: 100%;object-fit: contain" src=' + '{{ asset('img/datatables/icon_minus.png') }}' + ' ></img>');
                });
                //Set to false if you don't want the paging to reset after ajax load,otherwise true
            }, false);
        }, 1000);
    });

Retain selection on reload, Select uses this information to be able to retain row selection over a data reload. the Buttons extension which will call the ajax.reload() method when activated. To demonstrate Select's ability to retain the selected rows over a data reload,  Can I refresh datatable after change row data (update, delete..etc) with jquery / ajax? #211

Parent / child editing in child rows, To get us started we are going to revisit the Editor parent / child post. information can be submitted to the child table server-side script when a row is isShown() ) { // This row is already open - close it destroyChild(row); tr. event to trigger the "show" action for the child row, as the Ajax reload will cause it  To view The details of each option, including a code sample, simply click on the row. There are also a number of API plug-ins and Extras which extend the capabilities of DataTables. If you don't see what you are looking for here, it is possible that a plug-in has implemented the functionality you need.

Ajax loaded row details, We can attach the following event listener to perform the open / close action: isShown() to check if the row has a child row shown or not already. an Ajax request that will update that element once the data has loaded. Hello, I am using the Jquery DataTable plugin and I am looking into how I can refresh a parent-row after a child-row has been updated. As an example I have come up with the following simple applica

Child rows (show extra / detailed information), The DataTables API has a number of methods for attaching child rows to a parent wanted to show, possibly including, for example, an Ajax call to the server to obtain isShown() ) { // This row is already open - close it row.child.hide(); tr. In addition to the above code, the following Javascript library files are loaded for  Is there a way to add dynamic child rows to the parent row from another data frame? in R? meaning one parent row can have 6 (lets day) child rows based on data in parent row and another parent row can have 2 or may be no rows based on data in their respective parent rows.

Comments
  • I am stuck saving the state of the shown class of the tr: <tr role="row" class="odd shown">
  • can you explain me the specific problem that you are stuck in?
  • I succeeded to keep child rows opened. But now I'm stuck: I cannot close the opened row child by clicking on td.details-control (which open and should close the row child). Could you take a look at my code above?
  • Can I ask how keep child rows opened ? I am having the same issue, everytime the datatables reload I have to reopen the child rows manually.
  • @OlivierCervello you have to keep track of which child is opened and whenever you refresh list, open list's child of same position.
  • This is fantastic, thank you. I just needed to add '#' to the selectors in the parameters (can't edit only two characters)