Управление DOM с помощью JavaScript в современных браузерах и IE 11+
Прежде чем взглянуть на этот пример, рекомендуем посетить этот пост, чтобы узнать, как мы можем перетаскивать элементы в списке.
Теперь мы можем использовать ту же технику, чтобы применить к строкам таблицы. Основная идея
Давайте начнем с основной разметки таблицы:
<table id="table">
...
</table>
Как упомянуто в элементе Drag and drop в примере списка, нам нужно обработать три события:
mousedown
для первой ячейки любой строки, поэтому пользователь может щелкнуть и перетащить первую ячейку в каждой строкеmousemove
для document: это событие срабатывает, когда пользователь перемещает строку, и мы создадим и вставим строку заполнителя в зависимости от направления (вверх или вниз)mouseup
для document: это событие происходит, когда пользователь перетаскивает строку.Вот скелет этих обработчиков событий:
// 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);
};