Arrow key navigation on DataTables with Select and Scroller extensions (working code)

Arrow key navigation on DataTables with Select and Scroller extensions (working code)

neanderthilneanderthil Posts: 7Questions: 1Answers: 0

After countless hours searching for working code to accomplish this I finally gave up and started from scratch. This is very tricky to do as most examples I found were simply moving the .selected class to other rows which isn't how datatables works... I tried using the row indexes to manipulate to select rows using the provided methods and this did not work either. For some reason the indexes would not select the proper rows. I finally found a tip from allan in a thread to use .nodes() and after much JQuery work and experimentation this is what I came up with.

To use the keydown listener on the .dataTable class you must add tabindex="0" to your table tag.

<table style="width:100%" cellpadding="0" cellspacing="0" border="0" id="cinfocon" class="row-border" tabindex="0">

This is the only way unless you wish to listen on the entire document, which causes
other issues. You can add the following css override to get rid of the focus outline.

.dataTable:focus {
  outline: 0 !important;
}

You also much make sure that you have your rowHeight set correctly on the scroller options or the visible rows will be calculated incorrectly.

scroller: {
  rowHeight: 35
},

And finally here is the code.

$(function(){
  $('.dataTable').keydown(function (e) {
    switch(event.keyCode)
    {
      //arrow down
      case 40:
      var thisRow = $(this).DataTable().row( { selected: true } ).node();
      var nextRow = $(thisRow).next('tr');
      var nextRowIdx = $(nextRow).index();
      var displayIndexes = $(this).DataTable().scroller.page();
      var indexDiff = displayIndexes.end - displayIndexes.start;
      var rowCount = $(this).DataTable().rows().count();
      var scrollTo;

      if (!(nextRowIdx > rowCount)) $(this).DataTable().row(nextRow).select();

      if ((displayIndexes.end) > rowCount) {
        scrollTo = $(this).find('tbody > tr:not(\'.group\')').eq(rowCount - indexDiff);
        $(this).DataTable().row(scrollTo).scrollTo(false);
      } else {
        scrollTo = $(this).find('tbody > tr:not(\'.group\')').eq(displayIndexes.end);
        if (nextRow[0] == scrollTo[0]) $(this).DataTable().row(scrollTo).scrollTo(false);
      }

      e.preventDefault();
      break;
      //arrow up
      case 38:
      var thisRow = $(this).DataTable().row( { selected: true } ).node();
      var lastRow = $(thisRow).prev('tr');
      var lastRowIdx = $(lastRow).index();
      var displayIndexes = $(this).DataTable().scroller.page();
      var indexDiff = displayIndexes.end - displayIndexes.start;
      var scrollCheck = $(this).find('tbody > tr:not(\'.group\')').eq(parseInt(displayIndexes.start) - 1);
      var scrollTo;


      if (!(lastRowIdx < 0)) $(this).DataTable().row(lastRow).select();

      if ((displayIndexes.start - indexDiff) > 0) {
        scrollTo = $(this).find('tbody > tr:not(\'.group\')').eq(displayIndexes.start - indexDiff);
        if (lastRow[0] == scrollCheck[0]) $(this).DataTable().row(scrollTo).scrollTo(false);
      } else {
        scrollTo = $(this).find('tbody > tr:not(\'.group\')');
        if (lastRow[0] == scrollCheck[0]) $(this).DataTable().row(scrollTo).scrollTo(false);
      }

      e.preventDefault();
      break;
    }
  });
  $('.dataTable').focus();
});

This code will provide working arrow navigation for all datatables on a page and even accounts for rowGroups.

My hope is that this saves someone else a lot of time and effort... I was kinda blown away that no working code was around yet but after writing it I can see why. It's a PITA! Have a nice day. :)

Replies

  • neanderthilneanderthil Posts: 7Questions: 1Answers: 0

    Seems there is still some issues on tables with large datasets.

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

    So just to confirm - this uses arrow keys to move the selection of the row, rather than using something like KeyTable which focuses per cell, is that correct?

    If so, thanks for sharing your solution with us. Adding full row selection to KeyTable is something I've been wanting to get around to for ages!

    Allan

  • neanderthilneanderthil Posts: 7Questions: 1Answers: 0

    Correct... It moves the the selected row up or down.

    Unfortunately after more testing it seems to have problems on larger datasets after you get around 4-5 pages down. Also on deffered rendered pages when you reach the end of the current buffer it seems to have issues after the next buffer is loaded. I will probably revisit it soon to try to resolve these issues.

  • neanderthilneanderthil Posts: 7Questions: 1Answers: 0
    edited June 2018

    Redid this code far more elegantly... This works 100% perfect!

    Take note the scrollIntoViewIfNeeded() function is rather new and may not work on all browsers but seems to work fine in Chrome.

    $(function(){
      $('.dataTable').keydown(function (e) {
        switch(event.keyCode)
        {
          //arrow down
          case 40:
          var thisRow = $(this).DataTable().row( { selected: true } ).node();
          var nextRow = $(thisRow).next('tr');
          var nextRowIdx = $(nextRow).index();
          var rowCount = $(this).DataTable().rows().count();
    
          if (!(nextRowIdx > rowCount)) {
            if ($(nextRow).hasClass('group')) nextRow = $(nextRow).next('tr');
            $(this).DataTable().row(nextRow).select();
    
            var nextRowID = $(nextRow).attr('id');
            document.getElementById(nextRowID).scrollIntoViewIfNeeded(false);
          }
    
          e.preventDefault();
          break;
          //arrow up
          case 38:
          var thisRow = $(this).DataTable().row( { selected: true } ).node();
          var lastRow = $(thisRow).prev('tr');
          var lastRowIdx = $(lastRow).index();
    
          if (!(lastRowIdx < 0)) {
            if ($(lastRow).hasClass('group')) lastRow = $(lastRow).prev('tr');
            $(this).DataTable().row(lastRow).select();
    
            var lastRowID = $(lastRow).attr('id');
            document.getElementById(lastRowID).scrollIntoViewIfNeeded(false);
          }
    
          e.preventDefault();
          break;
        }
      });
      $('.dataTable').focus();
    });
    
This discussion has been closed.