Any Export Complete Event?

Any Export Complete Event?

mfitportalmfitportal Posts: 20Questions: 7Answers: 0
edited October 2019 in Buttons

Hi, I have created a custom action under the buttons for exporting either CSV/Excel files. The export takes quite a bit of time so I have added a loading modal to notify the user - however, I am looking for a way to detect when the export is complete.

If there is some type of event that is fired so I can attach an event listener and have it close the modal once export has completed, that would be great! I have also (unsuccessfully) tried to implement some sort of callback function or promise, however it simply executes the code immediately every time and does not wait for the export to finish.

Replies

  • kthorngrenkthorngren Posts: 20,144Questions: 26Answers: 4,736

    The buttons-processing might do what you want.

    Kevin

  • allanallan Posts: 61,446Questions: 1Answers: 10,054 Site admin
    edited October 2019

    What Kevin said :). Check for the indicator parameter being false to see when the processing is complete.

    Allan

  • mfitportalmfitportal Posts: 20Questions: 7Answers: 0

    Thank you, I will try that out and let you know if it works!

  • mfitportalmfitportal Posts: 20Questions: 7Answers: 0
    edited October 2019

    Hello, after implementing and testing it, it doesn't seem like the Datatables Export File functions (csv/html) are firing any buttons-processing events.

    I have checked to see if the handler is receiving any fired events when I click the export buttons- however, they are not.

    Manually adding processing(true/false) in the buttons custom action will not resolve my problem since the issue is that the next line of code immediately executes even before the export has completed

    Here is some sample code to show what I mean:

    action: function(e, dt, button, config) {

                            $('#loadingModal').modal('show');
    
                            // Call the original action function afterwards 
                            var that = this; // setTimeout function changes scope - save current scope as that
                            if ($.fn.dataTable.ext.buttons.csvHtml5.available( dt, config )) {
                              setTimeout(function(){
                                $.fn.dataTable.ext.buttons.csvHtml5.action.call(that, e, dt, button, config);
                              }, 1000);
                            }
                            else {
                              setTimeout(function(){
                                $.fn.dataTable.ext.buttons.csvFlash.action.call(that, e, dt, button, config);
                              }, 1000);
                            }
                            hideModal('#loadingModal'); // I want this line to execute AFTER the export is complete
    

    The final line immediately executes after the export line begins, it does not wait for the export to finish.

    I need some type of way to detect when either the export is complete or an exported file has been downloaded, so then the modal will be successfully hidden at the proper time.

  • kthorngrenkthorngren Posts: 20,144Questions: 26Answers: 4,736

    The buttons-processing works in this example:
    http://live.datatables.net/powoseza/1/edit

    Basically used the example in the docs but changed to console.log output.

    Maybe you can update my example to show the issue you are having.

    Kevin

  • mfitportalmfitportal Posts: 20Questions: 7Answers: 0

    Kevin,

    I implemented a very similar version as your example. For some reason, it still does not work.

    Could this be an issue regarding the version? I am using DataTables 1.10.18

  • kthorngrenkthorngren Posts: 20,144Questions: 26Answers: 4,736
    edited October 2019

    According to the buttons-processing documentation the feature was introduced into Buttons 1.5.7.

    Since: Buttons 1.5.7

    Kevin

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    @kthorngren
    Hi Kevin,
    I saw your example above and yes, it works.

    I have a simple Data Table that contains a more complex Excel export that can take a while. Hence I wanted to display a message informing the user or display a spinner while making the Excel file.
    I use sweet alert or busyLoad for these purposes. It turned out that with "buttons-processing" they aren't working. They don't get triggered. The only thing I got working - like in your example - was console.log and a simple alert.
    Do you have any idea why plugins don't get triggered only "native" Javascript? I have never come across this behavior with other Data Table events.

    This is my code:

    var ctrAuthLogTable = $('#tblCtrAuthLog').DataTable( {
        dom: "Bfrltip",
        select: false,
        ajax: {
            url: 'actions.php?action=tblCtrAuthLog',
            type: 'POST',
            data: function ( d ) {   
                d.startDate = nMonthsAgo( $('#periodSelected').val() );
            }
        },
        pageLength: 20,
        lengthMenu: [5, 10, 20, 50, 100, 200, 500],
        columns: [
            {   data: "log.changer" },
            {   data: "affected_user" },
            {   data: "log.action",
                render: function ( data, type, row ) {
                    return renderAction(data);
                }
            },
            {   data: "update_time",
                render: function (data, type, row) {
                    return renderDateTimeSeconds(data);
                }
            },
            {   data: "log.user_role",
                render: function ( data, type, row ) {
                    return renderRole(data);
                }
            },
            {   data: "user_dept" }
        ],
        order: [[ 3, 'desc' ]],
        buttons: [
              "colvis",
            { extend: "excel",
                title:    function () { return lang === 'de' ? 'Berechtigungs Log' : 'Authorization Log' },
                filename: function () { return lang === 'de' ? 'Berechtigungs Log' : 'Authorization Log' }
            },
            { extend: "excel",
                text:     function () { return lang === 'de' ? 'Excel Berechtigungs Änderungshistorie' : 'Excel Authorization Change History' },
                className:     "historyButton",
                name:          "historyButton",
                title:    function () { return lang === 'de' ? 'Berechtigungs Änderungshistorie' : 'Authorization Change History' },
                filename: function () { return lang === 'de' ? 'Berechtigungs Änderungshistorie' : 'Authorization Change History' },
                customize: function( xlsx ) {
                    var sheet = xlsx.xl.worksheets['sheet1.xml'];
                    var cols = $('col', sheet);
                    var rows = $('row c', sheet);
                    var i; var y; var ltr = 'A'; var colLtrs = [];
                    //fill the Excel column letters array
                    for ( i=0; i < cols.length; i++ ) {
                        colLtrs.push(ltr);
                        ltr = String.fromCharCode(ltr.charCodeAt() + 1); 
                    }
                    //the first two rows are heading, so we start with
                    //the fourth row and compare it with the third etc.
                    for ( i=3; i < rows.length; i++ ) {   
                        //loop through all columns within the row
                        for ( y=0; y < cols.length; y++ ) {
                            //we only hihglight if it is NOT a new user because a new user is not a change
                            if ( $('row:eq('+i+') c[r^="C"] t', sheet).text() != "Neu" &&
                                 $('row:eq('+i+') c[r^="C"] t', sheet).text() != "New"    ) {
                                //we always highlight the action column if a change occurred
                                if ( colLtrs[y] == "C" ) {
                                    $('row:eq('+i+') c[r^='+colLtrs[y]+']', sheet).attr( 's', '11' );  //white text red background
                                } else if ( $('row:eq('+i+') c[r^='+colLtrs[y]+'] t', sheet).text()     !=
                                            $('row:eq('+(i-1)+') c[r^='+colLtrs[y]+'] t', sheet).text()     ) {                                       
                                    // we don't highlight the editing time columns
                                    if ( colLtrs[y] != "D" ) {
                                        $('row:eq('+i+') c[r^='+colLtrs[y]+']', sheet).attr( 's', '20' );  //blue background
                                    }
                                }
                            }
                        }
                    }            
                },
                exportOptions: {
                    modifier: { order: 'index' } //order as returned from the server
                }
            }
        ]
    } );
    
    //the second table on the page takes very long to load; we want to make the first table visible quickly
    //and have both tables with adjusted columns!!
    ctrAuthLogTable
        .on( 'init', function () {
            $("h1:has(> img), h2:has(> img), h3:has(> img)").addClass("noBullet");
            $('#content').fadeIn('fast');        
            $.busyLoadFull("hide", { 
              // options here 
            }); 
            //"api: true" - means it is a data table
            $.fn.dataTable
                .tables( { visible: true, api: true } )
                .columns.adjust();
        } )
        .on( 'buttons-processing', function ( e, indicator, buttonApi, DataTable, node, config ) {
            if (indicator) {
                if ( config.className.indexOf("historyButton") >= 0 ) {
                    alert(lang === 'de' ? 
                        "Dieser Bericht braucht eine Weile. Bitte warten Sie bis der Bericht heruntergeladen ist." : 
                        "This report takes a while. Please wait until the report's download is completed.");
                } 
            }
        } );
        
    $('#periodSelected').change( function() {
        ajaxReloadTbls([ctrAuthLogTable]);
    } );
    
  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406
    edited February 2020

    Using this plugin it does not work:

    CSS:

    <!--Bootstrap Sweet Alert CSS--> 
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.css" rel="stylesheet" />
    
    

    Javascript:

    <!--Sweet Alert Bootstrap Javascript--> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-sweetalert/1.0.1/sweetalert.min.js"></script>
    

    It should work but nothing happens; only works with native Javascript alert:

    .on( 'buttons-processing', function ( e, indicator, buttonApi, DataTable, node, config ) {
        if (indicator) {
            if ( config.className.indexOf("historyButton") >= 0 ) {
                swal({
                    title: lang === 'de' ? 'Ihr Bericht wird erstellt!' : 
                                           'Your report is being created!',
                    text: lang === 'de' ? 
                        "Dieser Bericht braucht eine Weile. Bitte warten Sie \n\
                         bis der Bericht heruntergeladen ist." : 
                        "This report takes a while. Please wait until the \n\
                         report's download is completed.",
                    type: '',
                    showConfirmButton: false
                });
            } 
        } else {
            swal.close();
        }
    } );
    
  • kthorngrenkthorngren Posts: 20,144Questions: 26Answers: 4,736

    I updated the above example with Sweet Alert and it seems to work. The export is quick so its just a flash on the screen. I've not used it before I'll need to check it out.
    http://live.datatables.net/powoseza/19/edit

    Not sure why its not working in your case. Do you get errors in your browser's console?

    Kevin

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Thanks Kevin!

    No errors in the console, All fine.

    Just took a look at your example - and I guess I know what the problem is:
    Sweet Alert seems to get triggered on "mouseup" which doesn't happen for a lengthy Excel export until it is (almost) finished.Hence you only see it flashing briefly at the end which is completely useless. Same with "busyLoad" ...

    Actually I don't know why "mouseup" isn't happening for so long. I mean: Even if it takes a while to process something the button should not look "clicked" for such a long time?! (I am not a good front end programmer, sorry!)

    The simple Javascript alert seems to get triggered on "mousedown". That's why it works and the other solutions don't .

    Do you think this can be changed? Many thanks for your generous help, Kevin!

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406
    edited February 2020

    Thought about it again. I use Sweet Alert and busyLoad a lot when I do ajax calls etc. If this is in conjunction with clicks on buttons the buttons only look "clicked" really briefly because nothing much is happening in the browser / the front end.

    While with the Excel export a lot of processing is happening on the client machine - and the button doesn't look "clicked" only briefly but doesn't go up until the front end processing has finished.

    This seems to be the issue here. ... and how can we change this.

  • allanallan Posts: 61,446Questions: 1Answers: 10,054 Site admin

    I think what might be happening here is that the sweetalart is queued for display in the browser's renderer, but it buffers changes to try and make as few as possible (performance). Because a timeout can happen and the DOM update happens the export is triggered, resulting is the browser continuing to delay the redraw.

    Let's try a custom button that does the display of sweetalert and makes use of the default Excel export button in a small timeout:

            {
                text: 'Excel export',
                action: function ( e, dt, node, config ) {
                    // show sweetalert ...
     
                    setTimeout( function () {
                        $.fn.dataTable.ext.buttons.excelHtml5.action.call(this, e, dt, node, config);
    
                        // hide sweetalert
                    }, 50);
                }
            }
    

    You'll need to add the code for showing and hiding sweetalert.

    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Hi Allan,

    thank you! I tried it but it didn't work:
    - Sweet Alert was never hidden
    - Nothing got downloaded
    I also tried it with the confirm button and a subsequent spinner: Didn't work either. I reversed to my solution using a simple Java Script alert.

    Roland

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    It is actually the timout that causes nothing to be downloaded. I removed everything else to test it. With the timeout: no download. Without the timeout it works.

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    My use case is presenting the log to some expert users highlighting all of the changes made over time in the Excel export. To do this all with the log table (according to your blog example) is somewhat heavy lifting. My users can change the time to be shown using a button. On button click I do this ... and the use of "busyLoad" in this example would be exactly what I need for the Excel export as well:

    $('#periodSelected').change( function() {
        $.busyLoadFull("show", { 
            fontawesome: "fa fa-spinner fa-spin fa-3x fa-fw"
        });
        contractLogTable.ajax.reload( function(){
                                         $.busyLoadFull("hide", { 
                                             // options here 
                                         })
                                     }, false)
                                     .columns.adjust()
                                     .responsive.recalc();
    } );
    
  • allanallan Posts: 61,446Questions: 1Answers: 10,054 Site admin

    Oops - I should have uses a fat arrow function to preserve the context

    {
        text: 'Excel export',
        action: function ( e, dt, node, config ) {
            // show sweetalert ...
     
            setTimeout( () => {
                $.fn.dataTable.ext.buttons.excelHtml5.action.call(this, e, dt, node, config);
     
                // hide sweetalert
            }, 50);
        }
    }
    

    If that doesn't work, I'd need a link to a test case showing the problem.

    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Works!

    And no need for the buttons-processing event any longer.

    The only downside: Doesn't work with IE and polyfills don't help with it. Tried to rewrite it but couldn't make it work. Solution: IE users don't see the respective menu items ...

  • allanallan Posts: 61,446Questions: 1Answers: 10,054 Site admin
    {
        text: 'Excel export',
        action: function ( e, dt, node, config ) {
            var that = this;
            // show sweetalert ...
      
            setTimeout(function() {
                $.fn.dataTable.ext.buttons.excelHtml5.action.call(that, e, dt, node, config);
      
                // hide sweetalert
            }, 50);
        }
    }
    

    should work in legacy browser.

    Allan

  • rf1234rf1234 Posts: 2,801Questions: 85Answers: 406

    Thanks! That is really helpful. It works! (But IE is so slow with this I'll keep it blocked for those menu items.)

  • JFUJFU Posts: 4Questions: 1Answers: 0

    Hello!
    I'm trying this on SPFx and $.fn.dataTable.ext.buttons.excelHtml5 is not exists.

    index.ts from @types/datatables.net-buttons do not have excelHtml5, it's has csvHtml5 instead.

    Is any possiblity that @types/datatables.net-buttons will be updated?

    interface ExtButtonsSettings {
    collection: ExtButtonsCollectionSettings;
    csvHtml5: ButtonSettings;
    }

  • allanallan Posts: 61,446Questions: 1Answers: 10,054 Site admin

    You would probably need to send a pull request to DefinitelyTyped for that. We maintain our own typings for Buttons.

    Allan

This discussion has been closed.