Skip to content

Commit

Permalink
WebUI: Use event delegation to handle common table events
Browse files Browse the repository at this point in the history
Event delegation is now used to handle basic table events.
2 minor fixes were added to match GUI behavior:
* Clicking on the table body deselects everything
* Table rows are now scrolled into view when using up/down arrows

PR #21829.
  • Loading branch information
skomerko authored Nov 18, 2024
1 parent ea35aa4 commit c9c85ee
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 173 deletions.
12 changes: 4 additions & 8 deletions src/webui/www/private/css/dynamicTable.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
color: var(--color-text-white);
}

#transferList tr:hover {
cursor: pointer;
}

#transferList img.stateIcon {
height: 1.3em;
margin-bottom: -1px;
Expand All @@ -37,10 +33,6 @@
display: flex !important;
}

tr.dynamicTableHeader {
cursor: pointer;
}

.dynamicTable {
border-spacing: 0;
padding: 0;
Expand All @@ -54,6 +46,10 @@ tr.dynamicTableHeader {
white-space: nowrap;
}

.dynamicTable tr:hover {
cursor: pointer;
}

.dynamicTable tr:is(:hover, .selected) img:not(.flags) {
filter: var(--color-icon-hover);
}
Expand Down
4 changes: 0 additions & 4 deletions src/webui/www/private/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -861,10 +861,6 @@ td.statusBarSeparator {
color: var(--color-text-green);
}

.searchPluginsTableRow {
cursor: pointer;
}

#torrentFilesTableDiv .dynamicTable tr.nonAlt:hover {
background-color: var(--color-background-hover);
color: var(--color-text-white);
Expand Down
236 changes: 124 additions & 112 deletions src/webui/www/private/scripts/dynamicTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ window.qBittorrent.DynamicTable ??= (() => {
setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) {
this.dynamicTableDivId = dynamicTableDivId;
this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId;
this.dynamicTableDiv = document.getElementById(dynamicTableDivId);
this.fixedTableHeader = $(dynamicTableFixedHeaderDivId).getElements("tr")[0];
this.hiddenTableHeader = $(dynamicTableDivId).getElements("tr")[0];
this.tableBody = $(dynamicTableDivId).getElements("tbody")[0];
Expand All @@ -93,12 +94,81 @@ window.qBittorrent.DynamicTable ??= (() => {
},

setupCommonEvents: function() {
const tableDiv = $(this.dynamicTableDivId);
const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId);

const tableElement = tableFixedHeaderDiv.querySelector("table");
tableDiv.addEventListener("scroll", () => {
tableElement.style.left = `${-tableDiv.scrollLeft}px`;
this.dynamicTableDiv.addEventListener("scroll", function() {
tableElement.style.left = `${-this.scrollLeft}px`;
});

this.dynamicTableDiv.addEventListener("click", (e) => {
const tr = e.target.closest("tr");
if (!tr) {
// clicking on the table body deselects all rows
this.deselectAll();
this.setRowClass();
return;
}

if (e.ctrlKey || e.metaKey) {
// CTRL/CMD ⌘ key was pressed
if (this.isRowSelected(tr.rowId))
this.deselectRow(tr.rowId);
else
this.selectRow(tr.rowId);
}
else if (e.shiftKey && (this.selectedRows.length === 1)) {
// Shift key was pressed
this.selectRows(this.getSelectedRowId(), tr.rowId);
}
else {
// Simple selection
this.deselectAll();
this.selectRow(tr.rowId);
}
});

this.dynamicTableDiv.addEventListener("contextmenu", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

if (!this.isRowSelected(tr.rowId)) {
this.deselectAll();
this.selectRow(tr.rowId);
}
}, true);

this.dynamicTableDiv.addEventListener("touchstart", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

if (!this.isRowSelected(tr.rowId)) {
this.deselectAll();
this.selectRow(tr.rowId);
}
}, { passive: true });

this.dynamicTableDiv.addEventListener("keydown", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

switch (e.key) {
case "ArrowUp": {
e.preventDefault();
this.selectPreviousRow();
this.dynamicTableDiv.querySelector(".selected").scrollIntoView({ block: "nearest" });
break;
}
case "ArrowDown": {
e.preventDefault();
this.selectNextRow();
this.dynamicTableDiv.querySelector(".selected").scrollIntoView({ block: "nearest" });
break;
}
}
});
},

Expand Down Expand Up @@ -801,54 +871,6 @@ window.qBittorrent.DynamicTable ??= (() => {
tr.setAttribute("data-row-id", rowId);
tr["rowId"] = rowId;

tr._this = this;
tr.addEventListener("contextmenu", function(e) {
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
return true;
});
tr.addEventListener("click", function(e) {
e.preventDefault();

if (e.ctrlKey || e.metaKey) {
// CTRL/CMD ⌘ key was pressed
if (this._this.isRowSelected(this.rowId))
this._this.deselectRow(this.rowId);
else
this._this.selectRow(this.rowId);
}
else if (e.shiftKey && (this._this.selectedRows.length === 1)) {
// Shift key was pressed
this._this.selectRows(this._this.getSelectedRowId(), this.rowId);
}
else {
// Simple selection
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
return false;
});
tr.addEventListener("touchstart", function(e) {
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
}, { passive: true });
tr.addEventListener("keydown", function(event) {
switch (event.key) {
case "ArrowUp":
this._this.selectPreviousRow();
return false;
case "ArrowDown":
this._this.selectNextRow();
return false;
}
});

this.setupTr(tr);

for (let k = 0; k < this.columns.length; ++k) {
const td = new Element("td");
if ((this.columns[k].visible === "0") || this.columns[k].force_hide)
Expand All @@ -867,8 +889,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}

// Update context menu
if (this.contextMenu)
this.contextMenu.addTarget(tr);
this.contextMenu?.addTarget(tr);

this.updateRow(tr, true);
}
Expand All @@ -880,8 +901,6 @@ window.qBittorrent.DynamicTable ??= (() => {
trs.pop().destroy();
},

setupTr: (tr) => {},

updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? "full_data" : "data"];
Expand Down Expand Up @@ -1652,14 +1671,16 @@ window.qBittorrent.DynamicTable ??= (() => {
return filteredRows;
},

setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
e.preventDefault();
e.stopPropagation();
setupCommonEvents: function() {
this.parent();
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

this._this.deselectAll();
this._this.selectRow(this.rowId);
const row = this._this.rows.get(this.rowId);
this.deselectAll();
this.selectRow(tr.rowId);
const row = this.getRow(tr.rowId);
const state = row["full_data"].state;

const prefKey =
Expand All @@ -1679,9 +1700,7 @@ window.qBittorrent.DynamicTable ??= (() => {
startFN();
else
stopFN();
return true;
});
tr.addClass("torrentsTableContextMenuTarget");
},

getCurrentTorrentID: function() {
Expand Down Expand Up @@ -1920,10 +1939,6 @@ window.qBittorrent.DynamicTable ??= (() => {

return filteredRows;
},

setupTr: (tr) => {
tr.addClass("searchTableRow");
}
});

const SearchPluginsTable = new Class({
Expand Down Expand Up @@ -1955,10 +1970,6 @@ window.qBittorrent.DynamicTable ??= (() => {
}
};
},

setupTr: (tr) => {
tr.addClass("searchPluginsTableRow");
}
});

const TorrentTrackersTable = new Class({
Expand Down Expand Up @@ -2416,18 +2427,7 @@ window.qBittorrent.DynamicTable ??= (() => {
row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100)));
},

setupTr: function(tr) {
tr.addEventListener("keydown", function(event) {
switch (event.key) {
case "ArrowLeft":
qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId());
return false;
case "ArrowRight":
qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId());
return false;
}
});
}
setupCommonEvents: () => {}
});

const TorrentFilesTable = new Class({
Expand Down Expand Up @@ -2754,15 +2754,22 @@ window.qBittorrent.DynamicTable ??= (() => {
row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100)));
},

setupTr: function(tr) {
tr.addEventListener("keydown", function(event) {
switch (event.key) {
setupCommonEvents: function() {
this.parent();
this.dynamicTableDiv.addEventListener("keydown", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

switch (e.key) {
case "ArrowLeft":
qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId());
return false;
e.preventDefault();
window.qBittorrent.PropFiles.collapseFolder(this.getSelectedRowId());
break;
case "ArrowRight":
qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId());
return false;
e.preventDefault();
window.qBittorrent.PropFiles.expandFolder(this.getSelectedRowId());
break;
}
});
}
Expand Down Expand Up @@ -2805,12 +2812,14 @@ window.qBittorrent.DynamicTable ??= (() => {
}
window.qBittorrent.Rss.showRssFeed(path);
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
if (this.rowId !== 0) {
window.qBittorrent.Rss.moveItem(this._this.rows.get(this.rowId).full_data.dataPath);
return true;
}
setupCommonEvents: function() {
this.parent();
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
const tr = e.target.closest("tr");
if (!tr || (tr.rowId === "0"))
return;

window.qBittorrent.Rss.moveItem(this.getRow(tr.rowId).full_data.dataPath);
});
},
updateRow: function(tr, fullUpdate) {
Expand Down Expand Up @@ -2938,12 +2947,16 @@ window.qBittorrent.DynamicTable ??= (() => {
}
window.qBittorrent.Rss.showDetails(feedUid, articleId);
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
showDownloadPage([this._this.rows.get(this.rowId).full_data.torrentURL]);
return true;

setupCommonEvents: function() {
this.parent();
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

showDownloadPage([this.getRow(tr.rowId).full_data.torrentURL]);
});
tr.addClass("torrentsTableContextMenuTarget");
},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
Expand Down Expand Up @@ -3033,10 +3046,15 @@ window.qBittorrent.DynamicTable ??= (() => {
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
window.qBittorrent.RssDownloader.renameRule(this._this.rows.get(this.rowId).full_data.name);
return true;

setupCommonEvents: function() {
this.parent();
this.dynamicTableDiv.addEventListener("dblclick", (e) => {
const tr = e.target.closest("tr");
if (!tr)
return;

window.qBittorrent.RssDownloader.renameRule(this.getRow(tr.rowId).full_data.name);
});
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
Expand Down Expand Up @@ -3314,12 +3332,6 @@ window.qBittorrent.DynamicTable ??= (() => {

return filteredRows;
},

setupCommonEvents: () => {},

setupTr: (tr) => {
tr.addClass("logTableRow");
}
});

const LogPeerTable = new Class({
Expand Down
Loading

0 comments on commit c9c85ee

Please sign in to comment.