在查看此示例之前,建议访问此帖子以了解我们如何在列表中拖放元素。
现在我们可以使用相同的技术应用于表格行。基本思想是
- 当用户开始移动表行时,我们创建一个项目列表。每个项目都是从表的每一行克隆的。
- 我们在与表格相同的位置显示列表,并隐藏表格。
- 在这一步,移动行实际上是移动列表项。
- 当用户拖动一个项目时,我们确定目标项目在列表中的索引。并将原始拖动的行移动到与结束索引关联的行之前或之后。
让我们从表格的基本标记开始:
<table id="table">
...
</table>
基本设置
正如在列表中拖放元素示例中提到的,我们需要处理三个事件:
mousedown
对于任何行的第一个单元格,因此用户可以单击并拖动每行中的第一个单元格mousemove
fordocument
:当用户移动行时触发此事件,我们将根据方向(向上或向下)创建并插入占位符行mouseup
fordocument
:当用户拖动行时发生此事件。
以下是这些事件处理程序的框架:
// Query the table
const table = document.getElementById('table');
const mouseDownHandler = function(e) {
...
// Attach the listeners to `document`
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', mouseUpHandler);
};
const mouseMoveHandler = function(e) {
...
};
const mouseUpHandler = function() {
...
// Remove the handlers of `mousemove` and `mouseup`
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', mouseUpHandler);
};
// Query all rows
table.querySelectorAll('tr').forEach(function(row, index) {
// Ignore the header
// We don't want user to change the order of header
if (index === 0) {
return;
}
// Get the first cell of row
const firstCell = row.firstElementChild;
firstCell.classList.add('draggable');
// Attach event handler
firstCell.addEventListener('mousedown', mouseDownHandler);
});
当用户移动一行时克隆表
由于此任务执行一次,我们需要一个标志来跟踪它是否已执行:
let isDraggingStarted = false;
const mouseMoveHandler = function(e) {
if (!isDraggingStarted) {
isDraggingStarted = true;
cloneTable();
}
...
};
cloneTable
创建一个与表格具有相同位置的元素,并显示在表格之前:
let list;
const cloneTable = function () {
// Get the bounding rectangle of table
const rect = table.getBoundingClientRect();
// Get the width of table
const width = parseInt(window.getComputedStyle(table).width);
// Create new element
list = document.createElement('div');
// Set the same position as table
list.style.position = 'absolute';
list.style.left = `${rect.left}px`;
list.style.top = `${rect.top}px`;
// Insert it before the table
table.parentNode.insertBefore(list, table);
// Hide the table
table.style.visibility = 'hidden';
};
想象一下,它list
由从表行中克隆的项目组成:
const cloneTable = function() {
...
// Loop over the rows
table.querySelectorAll('tr').forEach(function(row) {
const item = document.createElement('div');
const newTable = document.createElement('table');
const newRow = document.createElement('tr');
// Query the cells of row
const cells = [].slice.call(row.children);
cells.forEach(function(cell) {
const newCell = cell.cloneNode(true);
newRow.appendChild(newCell);
});
newTable.appendChild(newRow);
item.appendChild(newTable);
list.appendChild(item);
});
};
在这一步之后,我们有以下内容list
:
<!-- The list -->
<div>
<!-- First item -->
<div>
<table>
<!-- The first row of original table -->
<tr>
...
</tr>
</table>
</div>
<!-- Second item -->
<div>
<table>
<!-- The second row of original table -->
<tr>
...
</tr>
</table>
</div>
<!-- ... -->
</div>
<!-- The original table -->
<table>
...
</table>
值得注意的是,在克隆每个项目中的单元格时,我们必须将单元格宽度设置为与原始单元格相同。所以该项目看起来完全像原始行:
cells.forEach(function (cell) {
const newCell = cell.cloneNode(true);
// Set the width as the original cell
newCell.style.width = `${parseInt(window.getComputedStyle(cell).width)}px`;
newRow.appendChild(newCell);
});
确定拖动和目标行的索引
let draggingEle; // The dragging element
let draggingRowIndex; // The index of dragging row
const mouseDownHandler = function (e) {
// Get the original row
const originalRow = e.target.parentNode;
draggingRowIndex = [].slice.call(table.querySelectorAll('tr')).indexOf(originalRow);
};
const mouseMoveHandler = function (e) {
if (!isDraggingStarted) {
cloneTable();
// Query the dragging element
draggingEle = [].slice.call(list.children)[draggingRowIndex];
}
};
const mouseUpHandler = function () {
// Get the end index
const endRowIndex = [].slice.call(list.children).indexOf(draggingEle);
};
正如我们有draggingRowIndex
和endRowIndex
,现在很容易检查用户是掉到表的顶部还是底部。我们可以决定如何在拖动行之前或之后移动目标行:
const mouseUpHandler = function () {
// Move the dragged row to `endRowIndex`
let rows = [].slice.call(table.querySelectorAll('tr'));
draggingRowIndex > endRowIndex
? // User drops to the top
rows[endRowIndex].parentNode.insertBefore(rows[draggingRowIndex], rows[endRowIndex])
: // User drops to the bottom
rows[endRowIndex].parentNode.insertBefore(rows[draggingRowIndex], rows[endRowIndex].nextSibling);
};
下面是最后的demo。尝试拖放任意行的第一个单元格。
演示
https://htmldom.dev/drag-and-drop-table-row/
相关博文
拖放表格行