用于基于电子的应用程序的类似 Wiki 的表格排序
Posted
技术标签:
【中文标题】用于基于电子的应用程序的类似 Wiki 的表格排序【英文标题】:Wiki-like table sorting for electron-based app 【发布时间】:2021-07-09 19:41:45 【问题描述】:我正在为Obsidian写一个插件(使用他们的API),希望实现wiki-like table sorting,即可以按升序(第一次点击),然后降序(第二次点击)对表格进行排序的可点击标题,然后终于恢复了原来的顺序(第三次点击)。
我编写了以下代码,该代码使用回调注册 click DOM 事件,该回调检查是否在表格内执行了点击,如果是,则对表格进行排序,主要是通过重新排序 tr
元素。但是,我不确定如何在第三次点击时恢复表格到原始状态。
this.registerDomEvent(document, 'click', (evt: MouseEvent) =>
const htmlEl = (<HTMLInputElement>evt.target);
const th = htmlEl.closest('thead th');
if (th == null) return;
const table = htmlEl.closest('table');
const tbody = table.querySelector('tbody');
const thArray = Array.from(th.parentNode.children);
const thIdx = thArray.indexOf(th);
// all other th's are reset
thArray.forEach((th, i) =>
if (i != thIdx)
th.removeAttribute("class");
);
// set clicked th class
th.className = this.classNames[
(this.classNames.indexOf(th.className) + 1) % this.classNames.length
];
const ascending = th.className === "header-sort-up";
Array.from(tbody.querySelectorAll('tr:nth-child(n)'))
.sort(this.compareFn(thIdx, ascending))
.forEach(tr => tbody.appendChild(tr));
);
我的一个想法是使用localStorage
来存储表的原始innerHTML
,但我认为这对于持久、长期使用和大量表来说不能很好地扩展。此外,如果可能的话,我不想添加额外的依赖项。
【问题讨论】:
您能否使用相关代码(minimal, working example)更新您的问题? 您是否只能访问 html 表格,还是可以选择在 javascript 中对数据进行排序,然后从那里生成表格? @Emaro - 我可以立即访问 HTML 表格。正如我所提到的,我的第一个解决方案是重新排序tr
元素。但是,我可以用表中的数据构造一个JavaScript对象,然后为排序后的表生成HTML,但是我仍然没有看到如何恢复原始顺序。
我不知道你的tr的结构,但也许你可以导出一个id,只存储id的原始顺序。
【参考方案1】:
对于这个问题,我将假设表格将有一个<thead>
(用于可点击元素)和一个<tbody>
。
与<table>
交互的原生JS API
要知道的一件好事是,JS 中的<table>
元素有一个特殊的接口HTMLTableElement,它允许我们轻松地执行一些选择/操作。所以一旦我们选择了我们的元素:
const table = document.querySelector('table')
我们可以访问它的整个结构。例如:
table.tHead
返回 <thead>
table.tBodies[0]
返回 <tbody>
table.tBodies[0].deleteRow(-1)
从正文中删除最后一个 <tr>
table.tBodies[0].rows[0].cells[0].cellIndex
返回tbody
的第一行(tr
)中第一个单元格(th
或td
)的索引
...
定义组件状态
此外,由于您描述的行为,我们知道我们将需要存储一些“状态”变量:
initialList
行的初始顺序
这很容易通过HTMLTableSectionElement 接口.rows
存储到一个数组中,我们再也不会碰它了
Array.from(tBody.rows)
currentOrder
对行进行排序的当前顺序(初始顺序、升序或降序)
为此,我将根据三个可能的顺序简单地使用0
、1
或2
。这使得迭代变得容易:我们只需要在每次订单更改时运行以下行:
currentOrder = (currentOrder + 1) % 3
currentSortReference
我们要用于对行进行排序的当前列
对于这个,我将存储用于排序的列的索引。要获得该值,我们只需要知道在<thead>
中单击的<tr>
的索引。 HTMLTableCellElement 接口 cellIndex
使这变得容易:
const index = currentTarget.cellIndex
基本实用功能
对于这样的问题,我喜欢从编写一些简单的实用函数开始。它们并不复杂,只需使用标准 JS(以及原生 HTMLTableElement 接口)。这里我们需要
emptyTable
清空表体
function emptyTable(tBody, rows)
rows.forEach(() => tBody.deleteRow(-1))
fillTable
用有序行填充表体
function fillTable(tBody, rows)
rows.forEach((row) => tBody.appendChild(row))
valueFromCell
从单元格中的文本中解析一个值
function valueFromCell(element)
return Number(element.textContent)
compareRows
比较 2 行的值(基于特定列中的值)。这是 Array.sort()
的比较函数,这就是它返回这些值的原因
function compareRows(a, b, index)
const valueA = valueFromCell(a.cells[index])
const valueB = valueFromCell(b.cells[index])
return valueA >= valueB
? 1
: -1
组件逻辑
现在我们已经有了所有这些部分,只需将它们放在一起即可:
点击事件onHeadClick
event.currentTarget.cellIndex
,如上所述)
如果与之前点击的相同(currentSortReference
),只需迭代顺序(currentOrder
)
如果不是,请重置订单
根据当前状态对表格进行排序(见下文)
function onHeadClick(currentTarget)
const index = currentTarget.cellIndex
if (currentSortReference === index)
currentOrder = (currentOrder + 1) % 3
else
currentSortReference = index
currentOrder = 1
sortTable(currentSortReference, currentOrder, tBody, initialList)
对表格进行排序sortTable
emptyTable
清空表
如果是初始订单,只需在表格 (fillTable
) 中填写 initialList
如果不是,则从initialList
创建一个新数组,并使用原生Array.sort
和我们在compareRows
之前定义的简单函数对其进行排序,然后填充表格(fillTable
)。
function sortTable(sort, order, tBody, rows)
emptyTable(tBody, rows)
if (order === 0)
fillTable(tBody, rows)
else
const newList = [...rows]
newList.sort((a, b) => compareRows(a, b, sort))
if(order === 2)
newList.reverse()
fillTable(tBody, newList)
解决方案
就是这样!代码有点多,但是一点点看,就很简单了。
const table = document.querySelector('table')
const heads = table.tHead.rows[0].cells
const tBody = table.tBodies[0]
let currentSortReference = null
let currentOrder = 0
const initialList = Array.from(tBody.rows)
Array.from(heads).forEach(th =>
th.addEventListener('click', onHeadClick)
)
/**
* @param MouseEvent event
*/
function onHeadClick(currentTarget)
const index = currentTarget.cellIndex
if (currentSortReference === index)
currentOrder = (currentOrder + 1) % 3
else
currentSortReference = index
currentOrder = 1
sortTable(currentSortReference, currentOrder, tBody, initialList)
/**
* @param Number sort index of head to base sort on
* @param 0 | 1 | 2 order 0: initial, 1: descending, 2: ascending
* @param HTMLTableSectionElement tBody
* @param HTMLTableRowElement[] rows all rows, in initial order
*/
function sortTable(sort, order, tBody, rows)
emptyTable(tBody, rows)
if (order === 0)
fillTable(tBody, rows)
else
const newList = [...rows]
newList.sort((a, b) => compareRows(a, b, sort))
if(order === 2)
newList.reverse()
fillTable(tBody, newList)
/**
* @param HTMLTableRowElement a
* @param HTMLTableRowElement b
* @param Number index of column to use in comparison
*/
function compareRows(a, b, index)
const valueA = valueFromCell(a.cells[index])
const valueB = valueFromCell(b.cells[index])
return valueA >= valueB
? 1
: -1
/**
* @param HTMLTableCellElement element
*/
function valueFromCell(element)
return Number(element.textContent)
/**
* @param HTMLTableSectionElement tBody
* @param HTMLTableRowElement[] rows
*/
function emptyTable(tBody, rows)
rows.forEach(() => tBody.deleteRow(-1))
/**
* @param HTMLTableSectionElement tBody
* @param HTMLTableRowElement[] rows
*/
function fillTable(tBody, rows)
rows.forEach((row) => tBody.appendChild(row))
<table>
<caption>Council budget (in £) 2018</caption>
<thead>
<tr>
<th scope="col">Items</th>
<th scope="col">Expenditure</th>
<th scope="col">Qty</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Donuts</th>
<td>3000</td>
<td>42</td>
</tr>
<tr>
<th scope="row">Stationery</th>
<td>18000</td>
<td>3</td>
</tr>
<tr>
<th scope="row">Chairs</th>
<td>100</td>
<td>248</td>
</tr>
</tbody>
</table>
【讨论】:
非常感谢您全面而清晰的解释!关于initialList
,我认为可以将其存储到localStorage
中?在这种情况下,大量的表不会有问题吗?或者如果有人以某种方式篡改了localStorage
?
initialList
是一个 DOM 节点数组,而 localStorage
只接受字符串,因此将其存储在 localStorage 中是行不通的。但是您可以编写一对方法来执行arrayOfDomNodesToString(initialList)
和initialList = stringToArrayOfDomNodes()
,以便您可以将其放入并从localStorage 中解析。
我明白了。因此,通常可以将localStorage
用于此类目的。不过,我想知道***是如何做到的,因为它似乎没有使用localStorage
?
不,只有在需要跨页面加载保持状态时才使用 localStorage。在这种情况下,将initialList
保留为变量可能没问题。您无需将其存储在任何地方。
啊,对..那是我的困惑!因此,在我的例子中,将有一个 initialList
数组,它将简单地驻留在 RAM 中。以上是关于用于基于电子的应用程序的类似 Wiki 的表格排序的主要内容,如果未能解决你的问题,请参考以下文章