Expanding all child rows from button

Expanding all child rows from button

bbrindzabbrindza Posts: 300Questions: 69Answers: 1

I have a DataTable that has 3 levels of child data. I use a click event on a +- image icon for each row to show or hide said rows.

I need to have the ability to expand all row from another button if a user desires.

I create a small function to mimic the individual +- row icons. However when I use the button to perform this operation, only the first child row expand. Any thoughts on this?


<input class="btn btn-primary btn-sm" type=button onclick="showAllRows(); return false;" value="Expand All Rows" > //***************************************************************************************** // New Expand All Rows function called from button //***************************************************************************************** function showAllRows(){ $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click'); $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click'); $('#salespersonBillTo td.BR_CBT_detail-level-control_3').trigger('click'); } //***************************************************************************************** // Event listener for opening and closing 1st level child details (Customer Bill To) //***************************************************************************************** $('#salespersonBillTo tbody').on('click', 'td.BR_CBT_detail-level-control_1', function () { var tr = $(this).closest('tr'); var row = table.row(tr); var BR_CBT_rowData1 = row.data(); if (row.child.isShown()) { // This row is already open - close it row.child.hide(); tr.removeClass('shown'); // Destroy the Child Datatable $('#salespersonBillTo_customerRows_' + BR_CBT_rowData1.returnedSalespersonGroupNumber).DataTable().destroy(); } else { // Open this row row.child(BR_CBT_format1(BR_CBT_rowData1)).show(); //********************************************************************************** // Event listener for opening and closing 2nd level child details (Products) //********************************************************************************** $('tbody').on('click', 'td.BR_CBT_detail-level-control_2', function () { var tr = $(this).closest('tr'); var row = salespersonBillTo_childTable1.row(tr); var BR_CBT_rowData2 = row.data(); if (row.child.isShown()) { // This row is already open - close it row.child.hide(); tr.removeClass('shown'); // Destroy the Child Datatable $('#salespersonBillTo_productRows_' + BR_CBT_rowData2.customerNumber).DataTable().destroy(); } else { // Open this row row.child(BR_CBT_format2(BR_CBT_rowData2)).show(); //******************************************************************************** // Event listener for opening and closing 3nd level child details (Budget Details) //********************************************************************************* $('tbody').on('click', 'td.BR_CBT_detail-level-control_3', function () { var tr = $(this).closest('tr'); var row = salespersonBillTo_childTable2.row(tr); var BR_CBT_rowData3 = row.data(); // console.log(BR_CBT_rowData3); if (row.child.isShown()) { // This row is already open - close it row.child.hide(); tr.removeClass('shown'); // Destroy the Child Datatable $('#salespersonBillTo_budgetRows_' + BR_CBT_rowData3.locationCode + '-' + BR_CBT_rowData3.customerNumber + '-' + BR_CBT_rowData3.productNumber).DataTable().destroy(); } else { // Open this row row.child(BR_CBT_format3(BR_CBT_rowData3)).show();

This question has an accepted answers - jump to answer

«1

Answers

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Without seeing it running its hard to say. But my first guess is a timing issue where the second and third level rows haven't been placed into the document yet when the trigger() function is called for them. If this is the case you might be able to use a short setTimeout() function to delay triggering the buttons.

    Another option might be to use rows().every() to open the child rows. This example is for one level:
    https://live.datatables.net/qolevune/204/edit

    Within the loop you can then open the second and third level rows.

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Kevin,

    Could I use this technique in a stand alone function? Or is this only used when the table is rendered?

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    The requirement for that example was to open all the rows at table initialization. You can use the code in a standalone function. Like this:
    https://live.datatables.net/qolevune/208/edit

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Kevin,
    Your example may not work for my needs due to the way the application is designed .

    The main Salesperson table uses an on click event to retrieve additional ajax data for the 1st child level table. until the event is triggered, the data for the 1st child level does not exist.

    I could build a live.datatables.net for the script but I would need to include the JSON data which I am not sure if this can been done in that environment.

    I can provide more of the code in this thread so you can better understand what I am trying to achieve.

    This is the reason way I was attempting to trigger the event with the following code.

    $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click');
    $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');

    It works fine for the 1st level , but does not trigger the second level. I will provide your with more code for the levels following this post.

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    I created a live.datatables.net with the code. Here is the link. https://live.datatables.net/qedatawe/1/edit

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774
    edited June 2023

    The test case doesn't run. Did you try my first suggestion using the setTimeout() function to delay triggering the buttons to open the second and third levels?

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    The test case is not running because it relies on AJAX source data from our internal server. Not sure how I can do this in a test case.

    Yes I did try this. It only expanded the first child.

    function showAllRows(){
            
            setTimeout(function() {
                  $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click');
                }, 2000); 
    
            
            setTimeout(function() {
                  $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
                }, 1000);
    
        }
    
  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    The problem is that the second setTimeout function executes immediately after the first in synchronous fashion. Meaning line 3 executes and enables a function that waits 2 seconds. But the Javascript code doesn't wait and executes line 8 and enables a function that waits 1 second. The second function executes 1 second later before the first.

    When I said a short time I meant milliseconds, ie, times of 5, 1 or 0. Try this:

    function showAllRows(){
             
            $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click'); 
             
            setTimeout(function() {
                  $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
    
                  setTimeout(function() {
                        $('#salespersonBillTo td.BR_CBT_detail-level-control_3').trigger('click');
                   }, 1);
    
              }, 1);
     
        }
    

    This will wait 1 ms after the first child buttons are clicked to click the second level. The second level function runs after 1 ms clicking the buttons then enabling the third level function to run 1 ms later.

    If this works it will only work on the page being displayed as the other rows are not in the document. So you might need to use the draw event to run the above code, something like this:

    var showAllFlag = false;
    function showAllRows(){
      showAllFlag = ! showAllFlag;  // toggle the show all flag
      $('#salespersonBillTo').DataTable().draw( false );
    }
    
    $('#salespersonBillTo').DataTable().on( 'draw', function () {
            $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click'); 
             
            setTimeout(function() {
                  $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
    
                  setTimeout(function() {
                        $('#salespersonBillTo td.BR_CBT_detail-level-control_3').trigger('click');
                   }, 1);
    
              }, 1);
     
        }
    

    One remaining potential problem is if a child row is open this technique might close it and open all the others.

    For the test case you can supply JSON data using Javascript sourced data like this example. You can grab a subset of the data using the browser's network inspector. Sanitize if needed and provide it using data on initialization or use rows.add() if adding after init.

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1
    edited June 2023

    I tried the last code example.

            var showAllFlag = false;
            function showAllRows(){
              showAllFlag = ! showAllFlag;  // toggle the show all flag
              $('#salespersonBillTo').DataTable().draw( false );
            }
             
            $('#salespersonBillTo').DataTable().on( 'draw', function () {
                    $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click');
                      
                    setTimeout(function() {
                          $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
             
                          setTimeout(function() {
                                $('#salespersonBillTo td.BR_CBT_detail-level-control_3').trigger('click');
                           }, 1);
             
                      }, 1);
              
                }   
    

    However it received console log errors..

    Uncaught SyntaxError: missing ) after argument list

    Uncaught ReferenceError: showAllRows is not defined

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Its a syntax error. Try adding a ) to the end of line 19, for example:

    var showAllFlag = false;
    function showAllRows(){
      showAllFlag = ! showAllFlag;  // toggle the show all flag
      $('#salespersonBillTo').DataTable().draw( false );
    }
      
    $('#salespersonBillTo').DataTable().on( 'draw', function () {
            $('#salespersonBillTo td.BR_CBT_detail-level-control_1').trigger('click');
               
            setTimeout(function() {
                  $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
      
                  setTimeout(function() {
                        $('#salespersonBillTo td.BR_CBT_detail-level-control_3').trigger('click');
                   }, 1);
      
              }, 1);
       
        });
    

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    syntax error want away.
    Getting the dreaded
    DataTables warning: table id=salespersonBillTo - Cannot reinitialise DataTable. For more information about this error, please see http://datatables.net/tn/3

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Sounds like you placed this code before initializing Datatables. In this case line 7 executes first initializing a default Datatable then your Datatables initialization executes causing the error. Move the code block in line 7 after you init your Datatable.

    Kevin

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Using the draw() event won't work because the row().child().show() API calls draw() which will cause an infinite loop to occur.

    If you are ok with just showing all on the page then put the code in the showAll function and remove the draw event handler.

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    I moved the block of code, the error went away but nothing happened when a clicked the Expand all button. I created NEW DataTable live bin with json data . Perhaps you can put some eyes on this and see what we are missing. https://live.datatables.net/kobumoye/1/edit

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774
    edited June 2023

    You are still trying to load data via ajax. When I comment out the ajax option no table shows. If you want help with that specific solution then please provide a running solution.

    I built a simple example to show both using the API and your trigger option:
    https://live.datatables.net/firerejo/1/edit

    Not the second child level doesn't exist. The trigger only opens the child rows for the existing page. The API version does show the first child level on all pages but not the second level.

    Kevin

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Also this won't work:

    $('#salespersonBillTo td.BR_CBT_detail-level-control_2').trigger('click');
    

    The id salespersonBillTo can exist only once on the page. Only the first element found with this ID will be tirggered.

    Kevin

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774
    edited June 2023

    I thought I dealt with the child rows and the draw event bubbling up to the parent before. See the last couple of posts in this thread for the solution. You can implement something similar and use the draw event like we discussed before.

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Thank you Kevin, I will play around with your suggestion . My apologies for over looking the ajax. You been helpful and a patient mate. I will let you know the out come of your suggest.

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    I updated the example to use the draw event with propagation turned off in the child rows. Here is the first option using trigger:
    https://live.datatables.net/firerejo/2/edit

    It works the first time but if you go back to a previous page it triggers again and closes all the rows. Might be a way to control this with a varaible.

    The second uses the row().child().show() and row().child.hide() APIs. The showAllFlag variable has three options; null, true or false. null means that a child row was clicked so don't check whether to show or hide all the rows. true means to show all the rows on the page in draw. false means to hide all rows on the page in draw. null turns off the show all/hide all functionality when a child row is clicked open or closed. It also uses row().child.isShown() to make sure the child row is open before closing. Otherwise the whole table disappears :smile:
    https://live.datatables.net/firerejo/3/edit

    Hopefully the second example will give you all you need to update your solution.

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    You have been very helpful with your explanations and examples. I will give your suggestion a try . Thanks again

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    Thanks. Some examples are more complex than others. Fortunately today was a light day so had more time to work on them :smile:

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Kevin,

    I took some time to play around with you last suggestion. It appears to work but not consistently. The Show All and Hide All does work, but only one time. When I try it again I receive the following error at the first child level 'Cannot read properties of undefined (reading 'customerNumber')' . This also occurs when I use the single + - image toggles.

    This is s time sensitive project that I need to complete so any of your wise advise and help would be greatly appreciated .

    By the why some of the styles that are in my live bin don't work like in my environment . Not a show stopper for troubleshooting , just wanted to point that out. You may not see the 1st level + image.

    Here is the my live DT Bin https://live.datatables.net/qagisada/1/edit

  • allanallan Posts: 61,744Questions: 1Answers: 10,111 Site admin

    Here is how I think I would approach this: https://live.datatables.net/qagisada/6/edit .

    There are a few differences from the previous bin:

    1) Rather than using a global variable for tracking what should be opened automatically, I've passed information around through the event handlers. For example, when triggering the child row from the parent, I do:

                $('td.child-control', this.node()).trigger('click', {
                    showChildren: true
                });
    

    There is then a check in the createChild_1 function to see if it should immediately open its own rows.

    2) That immediate opening is done in initComplete to account for Ajax loaded data. I also pass the API via the event handler for this one, which allows for Ajax or non-Ajax loading like this example.

    3) I've removed the id's from the tables so I could reuse the same data to test multiple rows, which you can see in the examples. With your real data, if it is important that you have the table id specified, you could add that in. Although I'd suggest avoiding doing so if you can. I couldn't see any need for them.

    4) I've updated the logic of showAll slightly so it accounts for rows which are already displayed or not, depending on if show or hide all was clicked.

    I think this is probably the most optimal way of doing what you are looking for.

    One word of caution though - if you have a lot of rows and you are making an Ajax call to get the data for each one, you need to make sure your server can handle the sudden load!

    Allan

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Alan,
    I played around with your bin and found that I am using ajax calls with a post method to get data for child ajax post parameters .

    I get the return returnedSalespersonGroupNumber data from the main table and need to pass it to the 1st child ajax call post parameters . This is to only get data for the returnedSalespersonGroupNumber .

    For the purpose of test case a have a variable var salespersonGroupingNumber set to 0 . The will be other records in the table that will need to be group by the salespersonGroupingNumber

    //Main Table 
    ajax: {
              type: 'POST',
              url: 'BudgetReporting/getBudgetReporting_SalepersonData.php',
              data: {salespersonGroupingNumber: salespersonGroupingNumber},
            },
    
    //Child Level 1
      ajax: { 
              type: 'POST',
              url: 'BudgetReporting/getBudgetReporting_CustomerBillToData.php',
              data: {salespersonGroupingNumber: BR_CBT_rowData1.returnedSalespersonGroupNumber},
            },
    
    
    //Child Level 2
     ajax: {
             type: 'POST',
             url: 'BudgetReporting/getBudgetReporting_CustomerProductsData.php',
             data: {salespersonGroupingNumber: BR_CBT_rowData2.returnedSalespersonGroupNumber,
                   customerNumber: BR_CBT_rowData2.customerNumber
            },
    

    This is my original script that is working in fine without using Expand and Collapse All .

    However I get an error that I am not sure how to resolve . When I expand a Child Row 1 (customers) and the expand another Child Row 1 (customers), I can not go back and expand Child Row 2 (products) because of Child Row 1 the data returned in my BR_CBT_rowData3 variable is from the last Child Row 1 (customers) expanded.

    VM15728:110 Uncaught TypeError: Cannot read properties of undefined (reading 'productNumber')
    at BR_CBT_format3 (<anonymous>:110:149)

    I create a bin with my original script and add some JSON data examples I do not know if the bin allows for ajax call.

    https://live.datatables.net/susaziyu/1/edit

    I am on a tight deadline to resolve this issue before next Wednesday and really could use your help.

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Alan ,
    I thought of a way of possibly doing this. If I use the data from the AJAX call I can set the row id with just the data values I need to open for the next child level and so forth and so on .

    Instead of defining my table row IDs as such 'salespersonBillTo_716' set them to the raw data ie. '716' . In the row click event I could set a variable with the data and use it in the AJAX post parameter for the next row.

    The next id would be the salesperson number with the customer number such as 716-12345. In the row click event for this row I would need to set a 2 variables by using string.split("-") and use those variables it in the AJAX post parameter for the next row.

    What are your thoughts on this approach, unless you have a better idea. .

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774
    edited July 2023

    VM15728:110 Uncaught TypeError: Cannot read properties of undefined (reading 'productNumber')

    This is similar to the error you are getting with your example from June 26:
    https://live.datatables.net/qagisada/1/edit

    That code looks similar to my working example. I'm not sure why the error occurs in your example. I remember a similar issue a few years ago but can't find the thread. There seems to be a reference that is getting lost somewhere in your solution.

    I create a bin with my original script and add some JSON data examples I do not know if the bin allows for ajax call.

    Yes the JS BIN environment allows for ajax requests. The problem is the JS BIN environment doesn't have access to the URL as it is not local. This error is in the browser's console:

    Failed to load resource: the server responded with a status of 403

    You could try using ajax as a function to return the appropriate rows. See this Scroller example. Just change the function to iterate the rows in the mock data to return the matched returnedSalespersonGroupNumber.

    Did you try Allan's suggestions of triggering the details button? It is a clean solution that works well. Your last test case doesn't have this code:

                initComplete: function () {
                    let api = this.api();
    
                    if (showChildren) {
                        api.rows().every(function () {
                            $('td.child-control', this.node()).trigger('click', {
                                api: api
                            });
                        });
                    }
                }
    

    This will replace the use of row().child().show() and row().child().hide().

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Kevin ,

    I did try Alans example from the last bin and the expand and collapse all rows buttons rows did work.

    However when I applied my AJAX using the required post parms is here I have issues with just expanding and collapsing individual row icons.

    When I click more than one 1st level child and then go back to the fist open child row icon to try expand the 2nd child level is where I receive that error.

    Two 1st level children expanded

    Click on 1st level child row 2 icon (error)

    Click on Child Level 2 2 child row icon (it works because this was the last 1st Child was expanded)

    My thought is that I lose the data from the BR_CBT_format3 variable first child expanded when I click to open another 1st child row

  • kthorngrenkthorngren Posts: 20,329Questions: 26Answers: 4,774

    I updated Allan's example to use ajax as a function to display the appropriate rows. I removed the data option. The solution still works as far as I can tell. Maybe you can apply your desired changes, like table ID, to this test case:
    https://live.datatables.net/qagisada/8/edit

    Kevin

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Thank you again Kevin for your suggestion . It appears this may do the trick. I will play around with it in my script later this afternoon in CST time .

    As always I highly appreciate all of your help and Alan's as well. Your support has always been timely and helpful.

    This is why DataTables and DataTables Editor is our company gold standard!

    I will keep you post :)

  • bbrindzabbrindza Posts: 300Questions: 69Answers: 1

    Hi Kevin,

    I looked at the sample code in the bin your provided , however I am not clear on how I would call my backend PHP script using AJAX and get the customersDataSet you use in your example.

    Seeing that the customersDataSet already exists as JSON in the script, this would make sense.

    //data: customersDataSet,
                ajax: function (data, callback, settings) {
                  var rows = [];
                  
                  for (i=0; i<customersDataSet.length; i++) {
                    if ( salespersonGroupingNumber === customersDataSet[i].returnedSalespersonGroupNumber ) {
                      rows.push(customersDataSet[i]);
                    }
                  }
                  callback({
                    data: rows,
                  });
                },
    

    However I need to still us an AJAX call to get data for my backend PHP script.
    How do I achieve this using your example?

    ajax: {
            type: 'POST',
            url: 'BudgetReporting/getBudgetReporting_CustomerProductsData.php',
             data: {salespersonGroupingNumber: BR_CBT_rowData2.returnedSalespersonGroupNumber,
                                       customerNumber: BR_CBT_rowData2.customerNumber
                                      },
                        },
    
Sign In or Register to comment.