Having to use setTimeout to pause before executing functions fired by DataTables events

Having to use setTimeout to pause before executing functions fired by DataTables events

jLinuxjLinux Posts: 981Questions: 73Answers: 75
edited October 2015 in Free community support

There have been plenty of cases when working with DataTables events that I have to find some work-around, because it seems like DataTables is firing off the event before its actually doing the event, or at least before its actually done... has anyone ran into this?

Heres an example, I use the the jQuery x-editable plugin to allow the contents of the table to be edited, however, whenever any new rows are introduced to the DOM after x-editable has already been initiated, then it needs to be re-initiated.

I threw the functionality to re-initiate x-editables inside a start_editable() function, and here is what I tried at first..

$assets_dt
    .on( 'order.dt', function () {
        start_editable();
    } )
    .on( 'length.dt', function ( e, settings, len ) {
        start_editable();
    } );

Which wasnt working at all, despite when I ran start_editable() via the JS console, it would work fine.

So I started finding stupid work around, like...

// Execute xEditables on pagination changes (Not sure why it wont work without this..)
$( "ul.pagination, ul.pagination > li, ul.pagination > li > a" ).click(function(){
    start_editable();
});

// Needs to also be initiated when the row limit is changed
$('.dataTables_length').find('select').change(function(){
    start_editable();
});

Then after having to repeatedly find stupid workarounds like the above for many different events, I thought that maybe the event was being fired off before the new elements in the dom were.. "Settled", if thats the right word. So I tried applying a setTimeout() with a very short timeout:

// x-editable needs to be re-initiated whenever new rows are introduced to the DOM..
$assets_dt
    // Ordering DataTables..
    .on( 'order.dt', function () {
        setTimeout(function(){
            start_editable();
        }, 5)
    } )
    // Changing the page length
    .on( 'length.dt', function ( e, settings, len ) {
        setTimeout(function(){
            start_editable();
        }, 5)
    } )
    // Changing the page itself
    .on( 'page.dt', function () {
        setTimeout(function(){
            start_editable();
        }, 5)
    } );

And it worked!

So what im asking is, is this a "problem"? or is it normal behavior? Im not a JS/jQuery professional by any means, I just like to code, so maybe this is normal. It just seems to me that the events should be fired off when the event is 100% completed.

This question has an accepted answers - jump to answer

Answers

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75
    edited October 2015

    And while im here...

    I run start_editable() by itself right after I initialize DataTables, but I think the "proper" way of doing it would be to have it run when DataTables fires the init, or even the draw.. So I have this setup:

    $assets_table
                // Initialization
                .on( 'init.dt', function () {
                    console.log('Init');
                    setTimeout(function(){
                        start_editable();
                    }, 50);
                } )
                // Redraw events
                .on( 'draw.dt', function () {
                    console.log('Draw');
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } )
                // Ordering columns
                .on( 'order.dt', function () {
                    console.log('Order');
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } )
                // Changing the page length
                .on( 'length.dt', function ( e, settings, len ) {
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } )
                // Changing the page itself
                .on( 'page.dt', function () {
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } );
    

    But it doesnt seem to work, the Init and Draw dont show up right away in the console, the Draw shows up when I do something else (sort, page, etc), but neither of them fire off when I first load the page... Is there a way to accomplish that?

    P.S. It seems clear that if I can get the init to work, then that along with the draw should be all I need, so I can drop the page, length and order

  • ThomDThomD Posts: 334Questions: 11Answers: 43

    Look at the InitComplete configuration option. That's where I run my ApplyEvents() code. The other place I've looked at that sort of things is in the footer draw call back function. I think I used to have some ties to .draw(), but I don't think I do anymore (it is at work).

    I did have some funny behavior in IE 8 where I would loose events after filtering. I fixed that with changes in my jQuery selectors.

    http://datatables.net/forums/discussion/29740/fixed-header-3-ie-11-in-8-mode-loss-of-events-jquery-selector-fun#latest

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    @ThomD Ill look at the initComplete option callback, but shouldnt the init.dt work as well?..

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    @ThomD So i tried the initComplete, and I ran into another issue with using that.

    So The DataTable is first initialized in the function I have assets.list(), this creates the HTML table, then initialized DataTable, then a second function is called, assets.editable(), which handles all of the editable features (This is because both functions are called in other times, without each other).

    I can re-initialize DataTables in assets.editable() to get a handle on it, but if I add any options to it, I get an error, saying

    DataTables warning: table id=data-table - Cannot reinitialize DataTable.

    Heres some of the current code, with the initComplete:

    assets = {
        list: function(){
            var $assets_table = $( '#data-table' );
    
            var $assets_dt = $assets_table.DataTable( {
                search: true,
                columns: columns,
                conditionalPaging: {
                    style: 'fade',
                    speed: 500
                },
                searchHighlight: true,
                buttons: [],
                select: {
                    style: 'multi'
                },
                language: {
                    "search": "Filter Assets"
                },
                lengthMenu: [
                    [5, 10, 15, 20, 25, 30, 40, 50, -1],
                    [5, 10, 15, 20, 25, 30, 40, 50, "All"]
                ],
                pageLength: 10,
                createdRow: function ( row, data, index ) {
                    wp.hooks.doAction('assets.datatable.createrow', {
                        row: row,
                        data: data,
                        index: index
                    });
                }
            } );
        },
        
        editable: function(){
            var $assets_table = $( '#data-table' );
    
            var $assets_dt = $assets_table.DataTable({
                // THIS throws the error
                initComplete: function(settings, json) {
                    console.log( 'DataTables has finished its initialisation.' );
                }
            });
            
            function start_editable(){
                // Stuff...
            }
    
            $assets_dt
                // Initialization
                .on( 'init.dt', function () {
                    // This doesnt seem to work at all, ever
                    console.log('Init');
                    setTimeout(function(){
                        start_editable();
                    }, 50);
                } )
                // Ordering columns
                .on( 'order.dt', function () {
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } )
                // Changing the page length
                .on( 'length.dt', function ( e, settings, len ) {
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } )
                // Changing the page itself
                .on( 'page.dt', function () {
                    setTimeout(function(){
                        start_editable();
                    }, 5);
                } );
            
        }
    };
    
    $(document).ready(function(){
        assets.list();
        assets.editable();
    });
    

    FYI, I really doubt it matters, but I tried moving all my event hooks above the line wiere DataTables is initialized (in assets.editable(), and changed the hooks CSS selector from $assets_dt to $assets_table, with no avail

  • ThomDThomD Posts: 334Questions: 11Answers: 43

    DT isn't going to like a simple reinitialization, but you can destroy and recreate if needed.

    http://datatables.net/reference/api/destroy()

    Why are you trying to reInit the DT object?

    Take a look a the API reference, there are all sorts of functions to reinit, reload, redraw, etc.

    http://datatables.net/reference/api/

    Reviewing my code (now that I'm at work), I only apply my on click events in the initComplete option. At one time I had it also triggered by .draw(), but I am not doing that anymore. The key for me is that all of my data is pulled down at once, so all of my processing (filter, sort, search) end with a .draw() event.

    I'd start with removing that re-Init part, hooking into InitComplete and see where that gets you. Then, maybe hooking into .draw() if needed.

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    I asked @allan how to get a handler for a table that was already initialized, but in a different function, he said "Just re-initialize it and you can use that", so it looks like DT doesnt mind simple reinitialization, its the non-simple reinitialization that it doesnt like, meaning any config options will make it throw a hissy fit.

    So you have your event handlers inside the iniComplete? wow, thats smart...
    However that doesnt change my issue, i would have to throw them inside the iniComplete in the 2nd function, not the function that DT is originally initialized in, so I would have the same error. And theres no way ill destroy it, that will just create issues in the other function, the one that originally initializes it.. lol.

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Im guessing my init isnt working because its trying to run in the 2nd function, meaning DT has already been initialized... hmm

  • ThomDThomD Posts: 334Questions: 11Answers: 43

    Time to step back and take a breath. :) Why do you want to reInit the table?

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Is there a way to hand down the DT instance to a function executed via the initComplete? So I dont have to execute it manually..

    Example (Doesnt work im sure):

    assets = {
        list: function(){
            var $dt = $('#example').DataTable( {
                initComplete: function(settings, json) {
                    assets.editable($dt);
                }
            } );
        },
        editable: function($dt){
            $dt
                // Initialization
                .on( 'init.dt', function () {
                    // This wont run...
                    console.log('Init');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                 // Redraw events
                 .on( 'draw.dt', function () {
                     console.log('Draw');
                     setTimeout(function(){
                        start_editable();
                     }, 5);
                 } )
                // Ordering columns
                .on( 'order.dt', function () {
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page length
                .on( 'length.dt', function ( e, settings, len ) {
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page itself
                .on( 'page.dt', function () {
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } );
        }
    };
    
  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    @thomd, so I can have a handler for it in a different function, but if I can accomplish what I posted above, then I wont need to

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    actually, I think this might work..

    initComplete: function(settings, json) {
        assets.editable(this);
    }
    

    Testing it, ill let ya know :-D

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75
    edited October 2015

    Well this is weird, everything seems to work as I expected, heres the code

    assets = {
        list: function(){
            var $dt = $('#example').DataTable( {
                initComplete: function(settings, json) {
                    assets.editable(this);
                }
            } );
        },
        editable: function($assets_dt){
            $assets_dt
                // Initialization
                .on( 'init.dt', function () {
                    // This wont run...
                    console.log('EVENT: init.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Ordering columns
                .on( 'order.dt', function () {
                    console.log('EVENT: order.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page length
                .on( 'length.dt', function ( e, settings, len ) {
                    console.log('EVENT: length.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page itself
                .on( 'page.dt', function () {
                    console.log('EVENT: page.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } );
        }
    };
    

    But theres an error when I re-order a column... heres the console logs

    EVENT: init.dt
    // Column re-ordered
    EVENT: order.dt
    Uncaught TypeError: Cannot read property 'on' of undefined
    

    That error is happening on the line with .on( 'init.dt', function () {... So why is it throwing an error in the init when that shouldnt even be firing off (its re-ordering, its already successfully initialized)

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    EDIT: Scratch the last error... assets.editable() was being called somewhere else, without the DT handler being handed down..

    So I got it all to work :-D!

    /thread

    Thanks @thomD !!

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Well crap.. I guess I was wrong. I was expecting this inside the initComplete and whatever variable I assigned the DataTables() to to be the same, which it isn't I guess.

    I did the following for debugging:

    var $assets_dt = $assets_table.DataTable( {
        search: true,
        columns: columns,
        conditionalPaging: {
            style: 'fade',
            speed: 500
        },
        searchHighlight: true,
        buttons: [
            // Have to add an empty buttons initiation, so buttons can be added later
        ],
        select: {
            style: 'multi'
        },
        language: {
            "search": "Filter Assets"
        },
        lengthMenu: [
            [5, 10, 15, 20, 25, 30, 40, 50, -1],
            [5, 10, 15, 20, 25, 30, 40, 50, "All"]
        ],
        pageLength: 10,
        createdRow: function ( row, data, index ) {
            wp.hooks.doAction('assets.datatable.createrow', {
                row: row,
                data: data,
                index: index
            });
        },
        initComplete: function( settings, json ) {
            console.log('---------------------------');
            console.log('initComplete',this);
    
            // Initialize the Editable, hand down the DT instance for event handling
            assets.editable(this);
        }
        //, dom: 'BT<"clear">lfrtip'//BT<"clear">lfrtip // Not sure I need this
    } );
    
    console.log('---------------------------');
    console.log('After',$assets_dt);
    

    And heres the console: http://d.pr/i/15wZb

    I was hoping it would work like that, because inside the assets.editable() function, I have some api functions, such as

    $assets_dt.column( 'modifier:name' ).visible( true, false );
    

    Which throws an error

    Uncaught TypeError: $assets_dt.column is not a function
    

    !@$(*@#%!@#

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    I got it to work by doing a crappy work-around (or atleast I think its crappy, is it?)

    Relevant code:

    assets = {
        editable: function ($dt) {
            "use strict";
    
            var $assets_table = $( '#data-table' );
    
            var $assets_dt = $assets_table.DataTable();
            
        
            // xEditable functionality for asset fields
            function start_editable(){
                // Lots and lots of code..
            }
    
            // Interval to wait before executing the xeditable init after the event is fired (Gotta be a bug)
            var dt_timeout = 5;
    
            // x-editable needs to be re-initiated whenever new rows are introduced to the DOM..
            // @note The editable init has been disabled in draw.dt, since that seems to execute more often than needed
            // and hog resources, slowing down the request. Just applying the start_editable() specifically where its
            // needed for now.
            $dt
                // Initialization
                .on( 'init.dt', function () {
                    // This wont run...
                    console.log('EVENT: init.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Ordering columns
                .on( 'order.dt', function () {
                    console.log('EVENT: order.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page length
                .on( 'length.dt', function ( e, settings, len ) {
                    console.log('EVENT: length.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } )
                // Changing the page itself
                .on( 'page.dt', function () {
                    console.log('EVENT: page.dt');
                    setTimeout(function(){
                        start_editable();
                    }, dt_timeout);
                } );
            
            // Use a plugin hook to update the table row after any update
            wp.hooks.addAction( 'assets.xeditable.success', function(data) {
                // Use the re-initialized datatable handler, as opposed to the param
                var $asset_row = $assets_table.find( 'tbody' ).find( 'tr[data-row-id="' + data.asset_id + '"]' );
    
                // Show the modified/modifier columns
                $assets_dt.column( 'modifier:name' ).visible( true, false );
                $assets_dt.column( 'modified:name' ).visible( true, false );
    
                // Update/draw modified datetime
                $assets_dt.cell( $asset_row.find( 'td[data-field="modified"]' ) ).data( modified ).draw();
    
                // Update/draw modifier link
                $assets_dt.cell( $asset_row.find( 'td[data-field="modifier"]' ) ).data( '<a href="/account/details/' + modifier + '">' + modifier_username + '</a>' ).draw();
            } );
        },
    
        list: function () {
            "use strict";
    
            // Cache the body handler
            var $body = $( 'body' );
    
            // Main reference for Assets List DataTable
            var $assets_table = $( '#data-table' );
    
            var columns = [];
    
            // Get the names of the columns (In the IDs of the first header)
            $assets_table.find( 'thead' ).eq( 0 ).find( 'tr' ).find( 'th' ).each( function ( i, v ) {
                //console.log('v:', $( this ).data('visible'));
                columns.push( {
                    name: $( this ).attr( 'id' ),
                    visible: $( this ).data( 'visible' ) != false
                } );
            } );
    
    
            // Initialize DataTable
            var $assets_dt = $assets_table.DataTable( {
                search: true,
                columns: columns,
                conditionalPaging: {
                    style: 'fade',
                    speed: 500
                },
                searchHighlight: true,
                buttons: [
                    // Have to add an empty buttons initiation, so buttons can be added later
                ],
                select: {
                    style: 'multi'
                },
                language: {
                    "search": "Filter Assets"
                },
                lengthMenu: [
                    [ 5, 10, 15, 20, 25, 30, 40, 50, - 1 ],
                    [ 5, 10, 15, 20, 25, 30, 40, 50, "All" ]
                ],
                pageLength: 10,
                createdRow: function ( row, data, index ) {
                    wp.hooks.doAction( 'assets.datatable.createrow', {
                        row: row,
                        data: data,
                        index: index
                    } );
                },
                initComplete: function ( settings, json ) {
                    console.log( '---------------------------' );
                    console.log( 'initComplete', this );
    
                    // Initialize the Editable, hand down the DT instance for event handling
                    assets.editable( this );
                }
                //, dom: 'BT<"clear">lfrtip'//BT<"clear">lfrtip // Not sure I need this
            } );
        }
    };
    

    So can anyone tell me the difference between what this is (inside the DataTables(), and $assets_dt? (as in var $assets_dt = $table.DataTable()

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75
    edited October 2015

    @ThomD

    Created a LiveDT instance: http://live.datatables.net/qihigezi/1/edit?js,output

    That works, so I guess all im asking is, is there a way to accomplish all that, without having to re-init the DT instance inside assets.editable() ?

    For example, I have to use $assets_dt.column( 1 ) in that example, I was hoping i could just use $dt.column( 1 ), and scrap the re-initialization inside assets.editable()

    P.S. Obviously if you wanna see the console log... you know what to do, lol

  • ThomDThomD Posts: 334Questions: 11Answers: 43
    edited October 2015

    Oh so close (ain't it always like that?). Inside the InitComplete option, "this" is the parent of the datatables object, so you need to append the .api() method.

    For example, here is how I add a column filter to the top of a few columns. Note that my "this" changes once I step into that "every" loop.

    initComplete: function () {
        this.api().columns('.dt-filter').every( function () {
            var column = this;
            var select = $('<select  multiple="multiple" class="mSelect"><option value=""></option></select>')
                .appendTo( $(column.header()) )
                .on( 'change', function () {
                    if ( $(this).val() == undefined) {
                        val = "";
                    } else {
                        var val = $.fn.dataTable.util.escapeRegex(
                            $(this).val().join("zzz")
                        );
                        val = val.replace(/zzz/g,"|");
                    }
                    column
                        .search( val ? '^' + val + '$'  : '' , true, false )
                        .draw();
                } );
            column.data().unique().sort().each( function ( d, j ) {
                select.append( '<option value="' +d+'">' +d+'</option>'  )
            } );
        } );
    

    Also, I would not try to .draw() individual cells. I use inline editing, so when I update a single row and I need to refresh the data, I make an ajax call. When the results come back, I run this.

    When I created the rows, I add an id attribute to the row that hold my key.

    var rowkey = '#row' + returnData.d.Id;
    //this updates the onscreen data with the retirned values
    dtTable.row(rowkey).data(returnData.d).draw();
    

    I can't help with the X-editable bits. Unless you really like it, you should take a look at the Editor library. It seemlessly integrates with DataTables. (Time is money.)

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75
    edited October 2015

    Hah, perfect! Thanks @ThomD, seems like its working 100% now!

    This is probably one of the more complicated set of functions ive coded, which was all to basically do the same thing that DataTables Edit does, but this is going to end up being an Open Source project :( or else I would definitely get it.

    Ill end up getting it anyways, might have a paid extension for it or something.

    If you see any more room for improvement in here (regarding DataTables, or anything I guess), just by glancing over, let me know! im no JS pro - http://code.linuxdigest.org/view/630f994e

  • allanallan Posts: 63,546Questions: 1Answers: 10,476 Site admin

    A lot of info here, so I've only scanned it - apologies if I've missed anything. @ThomD is absolutely correct - the execution scope of the callbacks is the HTML table element with an api() function attached to it to get the DataTables API instance.

    Another option is simply to attach the draw() event before you initialise the table:

    var myTable = $('#myTable')
      .on( 'draw.dt', function () {
        ...
      } )
      .DataTable( ... );
    

    Note that the first draw will happen before the DataTable() function has finished executing (and therefore myTable === undefined at that point).

    Allan

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Hey @allan. Sorry about the amount of info, the problem kinda morphed into something different than the original post.. lol. What the original problem was, and still is, is that I have to use setTimeout in any of the event handlers fired off by DataTable for them to work, if they are affecting things that are in the DOM, within DataTables.

    Example:

    $datatable
        // Initialization
        .on( 'init.dt', function () {
            setTimeout(function(){
                start_editable();
            }, 5);
        } )
        // Ordering columns
        .on( 'order.dt', function () {
            setTimeout(function(){
                start_editable();
            }, 5);
        } )
        // Changing the page length
        .on( 'length.dt', function ( e, settings, len ) {
            setTimeout(function(){
                start_editable();
            }, 5);
        } )
        // Changing the page itself
        .on( 'page.dt', function () {
            setTimeout(function(){
                start_editable();
            }, 5);
        } );
    

    If I dont do that, then they fire off before DataTables is done with whatever event its executing.

  • allanallan Posts: 63,546Questions: 1Answers: 10,476 Site admin
    Answer ✓

    I would suggest simply listening for the draw event. All of the events you list will proceed a draw event. Which also is why the required elements might not be immediately available in the DOM - they haven't been drawn yet:

    The order event states:

    Note that the page will be fired before the table has been redrawn with the updated data, although the data will internally have been ordered.

    That applies to the other events as well.

    Allan

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Ok, I know I tried this before..

    $dt
        // Re-draws (sort, page, length, etc)
        .on( 'draw.dt', function () {
            start_editable();
        } )
        // Initialization
        .on( 'init.dt', function () {
            start_editable();
        } );
    

    And the problem I had was that it was just wayyy to slow. Maybe I forgot to take the setTimeout out, because its out now and it works perfectly fine...

    Thanks @ThomD & @Allan!

This discussion has been closed.