luci-base: handleSort and UITable.update fix
authorPaul Donald <newtwen+github@gmail.com>
Thu, 19 Feb 2026 23:56:43 +0000 (00:56 +0100)
committerPaul Donald <newtwen+github@gmail.com>
Fri, 20 Feb 2026 00:29:43 +0000 (01:29 +0100)
Generated rows for a table need to land in the tbody
so make UITable.update align with this.

follow-up to 2da63d766a765ecae80a1d0a9849fb5f8c5aebb5

The mentioned commit restructured tables. Update sorting
to align with this. Otherwise window.requestAnimationFrame
puts sorted rows in the table element.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
modules/luci-base/htdocs/luci-static/resources/form.js
modules/luci-base/htdocs/luci-static/resources/ui.js

index 1fe7fe3ca36a432a413b4401410744c3a87133d6..3d87b3d7861b01a82656739f637e4d21c81bc904 100644 (file)
@@ -3636,32 +3636,34 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
         * @returns {null}
         */
        handleSort(ev) {
-               if (!ev.target.matches('th[data-sortable-row]'))
+               const th = ev.target && ev.target.closest ? ev.target.closest('th[data-sortable-row]') : null;
+               if (!th)
                        return;
 
-               const th = ev.target;
                const descending = (th.getAttribute('data-sort-direction') == 'desc');
                const config_name = this.uciconfig ?? this.map.config;
                let index = 0;
                const list = [];
 
-               ev.currentTarget.querySelectorAll('th').forEach((other_th, i) => {
+               const headerRow = ev.currentTarget;
+               headerRow.querySelectorAll('th').forEach((other_th, i) => {
                        if (other_th !== th)
                                other_th.removeAttribute('data-sort-direction');
                        else
                                index = i;
                });
 
-               ev.currentTarget.parentNode.querySelectorAll('tr.cbi-section-table-row').forEach(L.bind((tr) => {
+               const tableEl = headerRow.closest('table') || headerRow.parentNode;
+               tableEl.querySelectorAll('tr.cbi-section-table-row').forEach(L.bind((tr, i) => {
                        const sid = tr.getAttribute('data-sid');
                        const opt = tr.childNodes[index].getAttribute('data-name');
                        let val = this.cfgvalue(sid, opt);
 
                        tr.querySelectorAll('.flash').forEach((n) => {
-                               n.classList.remove('flash')
+                               n.classList.remove('flash');
                        });
 
-                       val = Array.isArray(val) ? val.join(' '): val;
+                       val = Array.isArray(val) ? val.join(' ') : val;
                        val = `${val}`; // coerce non-string types to string
                        list.push([
                                ui.Table.prototype.deriveSortKey((val != null && typeof val.trim === 'function') ? val.trim() : ''),
@@ -3679,9 +3681,11 @@ const CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection
                        let ref_sid;
                        let cur_sid;
 
+                       const tbodyEl = (tableEl.tBodies && tableEl.tBodies[0]) ? tableEl.tBodies[0] : tableEl;
+
                        for (let i = 0; i < list.length; i++) {
                                list[i][1].childNodes[index].classList.add('flash');
-                               th.parentNode.parentNode.appendChild(list[i][1]);
+                               tbodyEl.appendChild(list[i][1]);
 
                                cur_sid = list[i][1].getAttribute('data-sid');
 
index 7802fe076561b1648d465e229890fb1cd3a40156..706cc7f13b193cf281722a41a1e073949cdf22a9 100644 (file)
@@ -3901,8 +3901,10 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
                this.data = data;
                this.placeholder = placeholder;
 
+               const tbodyEl = (this.node.tBodies && this.node.tBodies[0]) ? this.node.tBodies[0] : this.node;
+
                let n = 0;
-               const rows = this.node.querySelectorAll('tr, .tr');
+               const rows = tbodyEl.querySelectorAll('tr, .tr');
                const trows = [];
                const captionClasses = this.options.captionClasses;
                const trTag = (rows[0] && rows[0].nodeName == 'DIV') ? 'div' : 'tr';
@@ -3933,16 +3935,16 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
 
                for (let i = 0; i < n; i++) {
                        if (rows[i+1])
-                               this.node.replaceChild(trows[i], rows[i+1]);
+                               tbodyEl.replaceChild(trows[i], rows[i+1]);
                        else
-                               this.node.appendChild(trows[i]);
+                               tbodyEl.appendChild(trows[i]);
                }
 
                while (rows[++n])
-                       this.node.removeChild(rows[n]);
+                       tbodyEl.removeChild(rows[n]);
 
-               if (placeholder && this.node.firstElementChild === this.node.lastElementChild) {
-                       const trow = this.node.appendChild(E(trTag, { 'class': 'tr placeholder' }));
+               if (placeholder && tbodyEl.firstElementChild === tbodyEl.lastElementChild) {
+                       const trow = tbodyEl.appendChild(E(trTag, { 'class': 'tr placeholder' }));
                        const td = trow.appendChild(E(tdTag, { 'class': 'td' }, placeholder));
 
                        if (typeof(captionClasses) == 'object')
@@ -4123,10 +4125,13 @@ const UITable = baseclass.extend(/** @lends LuCI.ui.table.prototype */ {
         * @param {Event} ev
         */
        handleSort(ev) {
-               if (!ev.target.matches('th[data-sortable-row]'))
+               let th = (ev && ev.target) ? ev.target : null;
+               if (th && typeof th.matches === 'function' && !th.matches('th[data-sortable-row]'))
+                       th = (typeof th.closest === 'function') ? th.closest('th[data-sortable-row]') : null;
+
+               if (!th)
                        return;
 
-               const th = ev.target;
                const direction = (th.getAttribute('data-sort-direction') == 'asc');
                let index = 0;