Search code examples
javascripttabulator

Tab behavior with "editor: 'list'" doesn't honor the tabindex


Tabulator's "editor: 'list'" seems to not honor the tabindex when you press 'Tab' when the select list menu is open.

https://jsfiddle.net/sv95rzy4/3/

To reproduce in the jsfiddle click on 'Alice' in the table then press the 'Tab' key. The menu opens in the Level column. Then press the 'Tab' key again. I would expect the button in the Action column to be selected, but instead the next row's dropdown menu opens.

Here is the code if you can't see the jsfiddle for some reason. I don't think it has anything to do with the fact I am using a formatter for the editor list, but I left it in the example just in case. (If you comment out the formatter the behavior is still the same).

var tableData = [{
    name: "Alice", level: "2", actions: "<button tabindex='0' onclick=\"alert('clicked 1')\">X</button>"},
  { name: "Bob", level: "3", actions: "<button tabindex='0' onclick=\"alert('clicked 2')\">X</button>", },
];

var levels = {  1: 'Pro',  2: 'Advanced',  3: 'Moderate',  4: 'Rookie'
};

var table = new Tabulator("#example-table", {
  height: '100px',
  data: tableData,
  columns: [{
    title: "Name",
    field: "name"
  },{
    title: "Level",
    field: "level",
    editor: "list",
    editorParams: {
      values: levels
    },
    formatter: function(cell, formatterParams, onRendered) {
      var select = document.createElement('select');
      select.style.width = '100%';
      select.innerHTML = '<option>' + levels[cell.getValue()] + '</option>'; 

      return select;
    }
  }, {title: "Actions", field: "actions", formatter: 'html'} ],
});

Solution

  • OK I figured it out!

    To fix forward tab movement you just need to add keybindings:false to the table definition.

    To fix backward tab movement (shift+tab) you need to do a few things. I think this is broken because the dynamically created input element for the dropdown editor causes some issue with the browsers tab/focus list.

    1. Add a listener to the relevant column. To make this easy I added a cssClass property to the column definition to ensure the listener would only see this column.
    },{
        title: "Level",
        field: "level",
        editor: "list",
        cssClass: "customClassName",
        editorParams: {
          values: levels
        },
    
    ...
    
    var elts = document.getElementsByClassName("tabulator-cell customClassName");
    for(let i=0; i < elts.length; ++i) {
        elts[i].addEventListener("keydown", function(e) {
            if(e.key === 'Tab' && e.shiftKey) {
                e.preventDefault();
                focusPrevElement();
            }
        }
    }
    
    1. Use a slightly modified version of this answer to create a "focusPrevElement()" function:
    function focusPrevElement () {
        //TODO: Change this based on what is in your layout:
        var focussableElements = 'button, img, input, select, .customClassName';
        if (document.activeElement) {
            var focussable = Array.prototype.filter.call(document.querySelectorAll(focussableElements),
            function (element) {
                //check for visibility while always include the current activeElement 
                return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
            });
            var index = focussable.indexOf(document.activeElement);
            if(index > 0) {
               // -2 is needed because of the dynamically created 'input' by Tabulator's cell editor module.
               var prevElement = focussable[index - 2] || focussable[0];
               prevElement.focus();
            }                    
        }
    }