ColumnControl - Auto focus the search input & select filtered results

ColumnControl - Auto focus the search input & select filtered results

hserveirahserveira Posts: 14Questions: 4Answers: 0
edited January 23 in ColumnControl

It seems I chose the incorrect category when posting this, and can't change or delete this post. Please help me change it :)

Users were asking for the search input inside the dropdown to be automatically focused, so they can start typing right after the dropdown opens, so I patched the dataTables.columnControl.js attachDropdown function. Here's the snippet:

function attachDropdown(dropdown, dt, btn) {
    // ... rest of the code

    // *** PATCH START *** [Line 242]
    // Auto focus the first input element in the dropdown
    var firstInput = dropdown.querySelector('input');
    if (firstInput) {
        firstInput.focus();
    }
    // *** PATCH END *** [Line 248]

    // ... rest of the code
}

Another requirement was quickly selecting the filtered options by pressing enter. For that, I patched the following functions:

    CheckList.prototype._redraw = function () {
        var buttons = this._s.buttons;
        var el = this._dom.buttons;
        var searchTerm = this._s.search.toLowerCase();
        el.replaceChildren();
        for (var i = 0; i < buttons.length; i++) {
            var btn = buttons[i];
            if (!searchTerm ||
                btn
                    .text()
                    .toLowerCase()
                    .includes(searchTerm)) {
                el.appendChild(btn.element());
            // *** PATCH START *** [Line 896]
            // Add a new 'visible' property which will be used on the selectFiltered function
                btn._s.visible = true;
            } else {
                btn._s.visible = false;                
            }
            // *** PATCH END *** [Line 901]
        }
        this._dom.empty.style.display = buttons.length === 0 ? 'block' : 'none';
        el.style.display = buttons.length > 0 ? 'block' : 'none';
    };
    function CheckList(dt, host, opts) {
         // ... rest of the code

        // *** PATCH START *** [Line 673]
        var searchKeypress = function (e) {
            if (e.key === 'Enter') {
                _this.selectFiltered();
                _this._s.handler(e, null, _this._s.buttons, true);
                _this._updateCount();
            }
        };
        // *** PATCH END *** [Line 681]

        // ... rest of the code

        // *** PATCH START *** [Line 690]
        // Listen for enter key on search to select filtered items
        dom.search.addEventListener('keypress', function (e) {
            if (e.key === 'Enter') {
                _this.selectFiltered();
                _this._s.handler(e, null, _this._s.buttons, true);
                _this._updateCount();
            }
        });
        // *** PATCH END *** [Line 699]

        dt.on('destroy', function () {
            // *** PATCH END*** [Line 705]
            dom.search.removeEventListener('keypress', searchKeypress);
            // *** PATCH START *** [Line 707]
        });
    }

And created this new function:

    /** PATCH START [Line 828]
     * Select filtered buttons
     *
     * @returns Self for chaining
     */
    CheckList.prototype.selectFiltered = function () {
        for (var i = 0; i < this._s.buttons.length; i++) {
            if (this._s.buttons[i]._s.visible) {
                this._s.buttons[i].active(true);
            }            
        }
        return this;
    };
    /** PATCH END */ [Line 841]

Replies

  • allanallan Posts: 65,506Questions: 1Answers: 10,880 Site admin
    edited January 23

    It seems I chose the incorrect category when posting this, and can't change or delete this post. Please help me change it

    Done. To be honest, I'm not sure how much the categories are used! I don't use them myself, just the front page and search.

    Users were asking for the search input inside the dropdown to be automatically focused, so they can start typing right after the dropdown opens

    Good idea - thanks for the suggestion and the patch. I'll look at adding that as an option.

    Another requirement was quickly selecting the filtered options by pressing enter. For that, I patched the following functions:

    I currently have "buttons" for "Select all" and "Deselect". I wonder if a "Select filtered" would do it, or perhaps the "Select all" should be changed to be "Select filtered" when a filter is applied?

    Allan

  • hserveirahserveira Posts: 14Questions: 4Answers: 0

    Thanks for your reply!
    Yes, I believe the "Select all" could be a little misleading if the user entered something on the search box, so having it change into "Select filtered" would be great.

  • hserveirahserveira Posts: 14Questions: 4Answers: 0

    Still on this topic, I came up with a solution to another common requirement: cascading the filters.

    This was achieved by creating a new function:

    function cascadeDropdown(dropdown, dt, btn) {  // Line 253
        // Get the column index from the button host
        let columnIndex = btn._s.host._s.columnIdx;
        // Get the data from the filtered rows only, so that the cascade effect works
        let rows = dt.rows({search: 'applied'}).indexes();
        let columnData = dt.cells(rows, columnIndex).data().toArray();
        // Transform into set for easier lookup
        let uniqueData = Array.from(new Set(columnData));
        let options = dropdown.querySelectorAll('button.dtcc-button');
    
        // Loop through each button in the dropdown and hide it if its value is not present in the column data
        options.forEach((button) => {
            const buttonValue = button.getAttribute('aria-label');
            const isValuePresent = uniqueData.some(data => {
                return data == buttonValue; // Use loose equality for type coercion
            });
            if (!isValuePresent) {
                button.style.display = 'none';
            } else {
                button.style.display = '';
            }
        });
    }  // Line 277
    

    And registering it inside dropdownContent:

    var btn = new Button(dt, this)
                .text(dt.i18n('columnControl.dropdown', config.text))
                .icon(config.icon)
                .className(config.className)
                .dropdownDisplay(liner)
                .handler(function (e) {
                // Do nothing if our dropdown was just closed as part of the event (i.e. allow
                // the button to toggle it closed)
                if (e._closed && e._closed.includes(dropdown)) {
                    return;
                }
                attachDropdown(dropdown, dt, config._parents ? config._parents[0] : btn);
                // PATCH START [Line 389]
                cascadeDropdown(dropdown, dt, config._parents ? config._parents[0] : btn);
                // PATCH END [Line 391]
                // When activated using a key - auto focus on the first item in the popover
                var focusable = dropdown.querySelector('input, a, button');
                if (focusable && e.type === 'keypress') {
                    focusable.focus();
                }
            });
    

    I'm sure it can be optimized somehow :)

  • hserveirahserveira Posts: 14Questions: 4Answers: 0

    I found out that putting cascadeDropdown after attachDropdown makes it not work very well along with selectNone. It seems like a better option is attach it to a search event on the table:

       dt.on('search.dt', function () {
           console.log('search event triggered')
           cascadeDropdown(dropdown, dt, btn);
       });
    

    It's working pretty well so far. I just noticed by adding the console.log that search.dt event fires quite a few times:

    Is this normal or did I mess up?

  • allanallan Posts: 65,506Questions: 1Answers: 10,880 Site admin

    Sounds like there is a loop somewhere. I wouldn't expect it to fire that many times.

    At the moment a cascade for the search in ColumnControl is not something I'm intending to add, but I am going to start on a rewrite of SearchPanes soon, and will be looking at how it might be integrated with ColumnControl and if there is scope there for that functionality.

    Allan

  • allanallan Posts: 65,506Questions: 1Answers: 10,880 Site admin

    For auto focus on the search input in the dropdown, I've committed a change to add that ability. It will be in the next major release of ColumnControl, which will ship alongside DataTables 3 (which is where all new feature development is currently going).

    Allan

  • allanallan Posts: 65,506Questions: 1Answers: 10,880 Site admin
    edited January 26

    Regarding the Select All issue, I've committed a change for that as well, renaming "Select all" to simply "Select" and having it select the visible options.

    Deselect isn't "smart" in that regard, it just deselects everything, which I think would be the most common use case / expectation.

    Allan

Sign In or Register to comment.