Plugin attaches itself to incorrect DataTable instance(s)

Plugin attaches itself to incorrect DataTable instance(s)

jLinuxjLinux Posts: 981Questions: 73Answers: 75
edited November 2015 in Plug-ins

Update: Switch to 2nd post to get the more accurate details.

This one might be difficult to explain..

Im using my new Live Ajax Plugin to monitor any changes in the ajax.url source of a DT instance.

This particular DT instance uses child rows, and in those child rows lies other DT instances, (which do not use the Live Ajax plugin). When these child rows are expanded, the "Child DataTable Instances" are created, and when the rows are collapsed, those child DT instances get destroyed via destroy().

Now, the Live Ajax Plugin monitors the destroy.dt via one() for the table that it was initiated on, when it sees that the table was destroyed, it will then quit monitoring the AJAX source for changes.

Problem: When the Child DT Instances are destroyed (due to the containing child row being collapsed) within the "parent" table, somehow, the destroy.dt monitor within the Live Ajax Plugin sees that and quits monitoring the AJAX source, even though its a different table being destroyed.

Has anyone ever ran into this? The destroy.dt attaches to the API that was created here within the plugin, but that shouldn't cause any issues would it?

This question has an accepted answers - jump to answer

Answers

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

    Oh, I was able to create a JSBin instance of it.

    To Replicate..

    1. Go to the JSBin
    2. Run JS
    3. Click the (+) on the parent row to expand the child row and initialize the Child DT Instance within it
    4. Click the (+) on the parent row again to collapse the child row & destroy the child DT Instance

    When you run the above steps, you should get an alert, which is generated by the pluginTest code at the top of the JS. The alert is generated on line 17, which is executed by the destroy on line 16. That destroy event should be tied to an API instance that was created on line 13, that API reference should reference the Parent Table..

    Heres the weird part: That alert that fires from within the pluginTest code, should only fire whenever the table that has pluginTest initiated on it gets destroyed, which you can see on line 32 that I'm initiating the DT instance with pluginTest on the Parent Table (#example1), however, when I destroy the Child Table (#childTable), the destroy gets fired...

    Basically.. You should never see that alert, as it should only be fired when the Parent Table gets destroyed, and as you can see on line 55, I'm destroying the Child, not the Parent...

    Im including the JS code below, so you can see what line numbers in referencing, since the JSbin doesnt include line numbers

    ( function( window, document, $, undefined ) {
        // On DT Initialization
        $(document).on('init.dt', function(e, dtSettings) {
            if ( e.namespace !== 'dt' )
                return;
    
            // LiveAjax options
            var options = dtSettings.oInit.pluginTest;
    
    
            // True if 'true' or any type of object, and is an ajax table
            if (options === true || $.isPlainObject(options) ) {
                var api = new $.fn.dataTable.Api( dtSettings );
    
                // If the DT instance was terminated, end the loop
                api.one('destroy.dt', function () {
                    alert('Destroy event detected');
                } );
            }
        });
    })( window, document, jQuery );
    
    
    $(document).ready( function () {
      var childTable;
      
      var parentTable = $('#example1').DataTable({
        info: false,
        searching: false,
        lengthChange: false,
        paging: false,
        pluginTest: true,
        columns: [
          {
            width: '50px',
            className:      'details-control',
            orderable:      false,
            defaultContent: '(+)'
          }, {
            title: 'A',
            name:  'A'
          }, {
            data:  'B',
            name:  'B'
          }
        ],
      });
      
      $('#example1 tbody').on('click', 'td.details-control', function () {
            var tr = $(this).closest('tr');
            var row = parentTable.row( tr );
    
            if ( row.child.isShown() ) {
              // Destroy CHILD DT (which does NOT have pluginTest enabled)
              childTable.destroy();
              
              // This row is already open - close it
              row.child.hide();
              tr.removeClass('shown');
            }
            else {
              // Open this row
              row.child( format() ).show();
              tr.addClass('shown');
              
                // Start the sub DT
              childTable = $('#childTable').DataTable({
                lengthChange: false,
                searching: false,
                info: false,
                paging: false
              });
            }
        } );
    
        function format(){
          return '<table id="childTable" width="100%"><thead><tr><th>Sub DT head</th></tr></thead><tbody><tr><th>Sub DT body</th></tr></tbody></table>';
        }  
    } );
    

    @allan - Do you think you can help at all with this?.. Thanks man

  • allanallan Posts: 63,489Questions: 1Answers: 10,470 Site admin

    So the issue here is the bubbling of the events. Because DataTables uses jQuery to trigger events, and jQuery in turn uses synthetic DOM events, the events will bubble up through the document.

    At the moment, to prevent this you would need to add a destroy event listener to the child table and then call event.stopPropagation(): http://live.datatables.net/rabijuku/3/edit . That will stop the event bubbling up to the parent table.

    Another option is to add a check into the destroy event handler to make sure that it is actually operating on the expected table.

    Without question this is a bug in the current design. The next major version of DataTables is going to rework the events and only a few (init is the only one I'm certain of) will be allowed to bubble.

    Allan

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Without question this is a bug in the current design.

    Sucks its a bug, but good to know that it wasn't just a bug I couldnt figure out. This one had me banging my head against the wall for a while.

    Another option is to add a check into the destroy event handler to make sure that it is actually operating on the expected table.

    This is what I was tempted to do.. Probably just best to check for the ID I suppose. Should I just change it from one() to on(), then check if the ID of the table being destroyed is the same as the ID of the table tied to the API object created on line 13? Then once I allow it to destroy the table, since the events are just regular jQuery events, I can detach the event listener right? (If so, would you suggest .detach() or .unbind()? Guessing the former)

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

    At the moment, to prevent this you would need to add a destroy event listener to the child table and then call event.stopPropagation()

    Ill probably do the other approach, since thats easier to control via the plugin, and it wouldn't require people to add an event handler to fire the stopPropagation() themselves.

    Thanks though, thats a good thing to know!

  • allanallan Posts: 63,489Questions: 1Answers: 10,470 Site admin
    Answer ✓

    $().detach() will remove an element from the DOM without removing tis event listeners, while $().unbind() will remove event listeners from an element. They do different things. $().off() is what I normally use to remove event listeners.

    Allan

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    Ok, is that the best route then?

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    @allan - How would I determine if whats being destroyed within..

    api.on('destroy.dt', function () {
        clearInterval(updates_loop);
    } );
    

    I tried to check if it was the correct table via $(api.table().node() ).attr('id'));, but that always returns the correct (Parent) table, not the DT instance in the child row

  • allanallan Posts: 63,489Questions: 1Answers: 10,470 Site admin
    api.on('destroy.dt', function (e, ctx) {
       console.log( ctx.nTable.id );
    } );
    

    gives you the table id.

    Allan

  • jLinuxjLinux Posts: 981Questions: 73Answers: 75

    perfect!! thanks man

This discussion has been closed.