javascriptjquerytabulator

Conflict between selectableRange and rowSelection: Clicking checkbox deselects other rows


I am using Tabulator (v6.3) and facing a conflict between the selectableRange module and the rowSelection formatter (checkbox).

The Goal: I want to use Excel-like range selection (selectableRange: 1) for cell operations, but also use a checkbox column (formatter: "rowSelection") for selecting multiple rows.

The Problem: I am trying to implement a checkbox column using a custom formatter to prevent the selectableRange click conflict. I added e.stopPropagation() to the checkbox click/mousedown events to stop the range selection logic from clearing other rows.

Current Behavior:

It seems that even though I stopped the event propagation, calling row.toggleSelect() programmatically still triggers Tabulator's internal single-selection logic enforced by selectableRange: 1. This creates a desync where the UI shows multiple selections, but the internal data only holds one.

My Constraints: I cannot modify the existing getSelectedData() calls in my legacy codebase. The native selection API must work. I need selectableRange for cell copying and rowSelection for multi-row actions to coexist.

Question: Is there a configuration option or a workaround to isolate the rowSelection column from the selectableRange logic? I need the checkboxes to toggle row selection additively without clearing the existing selection, while keeping selectableRange active for other columns.

// Data: Minimal set to reproduce the issue
var tableData = [
    {id:1, name:"Oli Bob", age:12},
    {id:2, name:"Mary May", age:1},
    {id:3, name:"Christine Lobowski", age:42},
];

// Custom Formatter to stop event propagation
var customRowSelection = function(cell, formatterParams, onRendered){
    var checkbox = document.createElement("input");
    checkbox.type = 'checkbox';
    
    // Attempting to sync with row selection
    checkbox.checked = cell.getRow().isSelected();

    // Trying to stop the range selection trigger
    var stopEvent = function(e){ e.stopPropagation(); }
    
    checkbox.addEventListener("pointerdown", stopEvent);
    checkbox.addEventListener("mousedown", stopEvent);
    
    checkbox.addEventListener("click", function(e){
        e.stopPropagation();
        cell.getRow().toggleSelect();
    });

    return checkbox;
};

// Tabulator Configuration
var table = new Tabulator("#example-table", {
    data: tableData,
    height: "200px",
    
    // 1. Enabling Range Selection (The core of the conflict)
    selectableRange: 1, 
    selectableRangeColumns: true,
    selectableRangeRows: true,
    
    // 2. Disabling Default Row Selection logic
    selectable: false,

    columns:[
        {
            // Custom checkbox column
            formatter: customRowSelection, 
            width: 50, 
            hozAlign: "center", 
            headerSort: false
        },
        {title:"Name", field:"name", width:200},
        {title:"Age", field:"age"},
    ],
});

// Issue: Even with stopPropagation, clicking the checkbox seems to trigger
// the internal range selection logic, clearing other selected rows.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/js/tabulator.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/css/tabulator.min.css" rel="stylesheet"/>

<div id="example-table"></div>


Solution

  • I was able to get it to work by modifying your custom formatter to manually select and unselect rows using jQuery to check the state of the checkbox that was clicked. I could not find the reason why the toggleSelect() clears the selected rows nor was I able to find a configuration that would allow it to work.

    var customRowSelection = function(cell, formatterParams, onRendered){
        var checkbox = document.createElement("input");
        checkbox.type = 'checkbox';
        checkbox.id = 'row_' + cell.getRow().getPosition();
        
        checkbox.addEventListener("click", function(e){
            if($('#' + e.target.id).is(":checked")) {
              table.selectRow(cell.getRow().getData().id);
            } else {
              table.deselectRow(cell.getRow().getData().id);
            }
        });
    
        return checkbox;
    };