Управление 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 header cells
table.querySelectorAll('th').forEach(function(headerCell) {
// Attach event handler
headerCell.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();
// 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() {
...
// Get all cells
const originalCells = [].slice.call(table.querySelectorAll('tbody td'));
const originalHeaderCells = [].slice.call(table.querySelectorAll('th'));
const numColumns = originalHeaderCells.length;
// Loop through the header cells
originalHeaderCells.forEach(function(headerCell, headerIndex) {
const width = parseInt(window.getComputedStyle(headerCell).width);
// Create a new table from given row
const item = document.createElement('div');
item.classList.add('draggable');
const newTable = document.createElement('table');
// Header
const th = headerCell.cloneNode(true);
let newRow = document.createElement('tr');
newRow.appendChild(th);
newTable.appendChild(newRow);
const cells = originalCells.filter(function(c, idx) {
return (idx - headerIndex) % numColumns === 0;
});
cells.forEach(function(cell) {
const newCell = cell.cloneNode(true);
newRow = document.createElement('tr');
newRow.appendChild(newCell);
newTable.appendChild(newRow);
});
item.appendChild(newTable);
list.appendChild(item);
});
};
После этого шага мы имеем следующее list:
<!-- The list -->
<div>
<!-- First item -->
<div>
<table>
<!-- The first column of original table -->
<tr>...</tr>
<tr>...</tr>
...
</table>
</div>
<!-- Second item -->
<div>
<table>
<!-- The second column of original table -->
<tr>...</tr>
<tr>...</tr>
...
</table>
</div>
<!-- ... -->
</div>
<!-- The original table -->
<table>
...
</table>
Стоит отметить, что при клонировании ячеек в каждом элементе мы должны установить ширину ячейки, равную исходной ячейке. Таким образом, элемент выглядит полностью как исходный столбец:
originalHeaderCells.forEach(function(headerCell, headerIndex) {
// Get the width of original cell
const width = parseInt(window.getComputedStyle(headerCell).width);
newTable.style.width = `${width}px`;
cells.forEach(function(cell) {
const newCell = cell.cloneNode(true);
newCell.style.width = `${width}px`;
...
});
});
let draggingEle; // The dragging element
let draggingRowIndex; // The index of dragging column
const mouseDownHandler = function(e) {
// Get the index of dragging column
draggingColumnIndex = [].slice.call(table.querySelectorAll('th')).indexOf(e.target);
};
const mouseMoveHandler = function(e) {
if (!isDraggingStarted) {
cloneTable();
// Query the dragging element
draggingEle = [].slice.call(list.children)[draggingColumnIndex];
}
};
const mouseUpHandler = function() {
// Get the end index
const endColumnIndex = [].slice.call(list.children).indexOf(draggingEle);
};
У нас есть draggingColumnIndex
и endColumnIndex
, теперь легко проверить, если пользователь отпускает элемент слева или справа от таблицы. И мы можем решить, как переместить целевой столбец до или после перетаскиваемого столбца:
const mouseUpHandler = function() {
// Move the dragged column to `endColumnIndex`
table.querySelectorAll('tr').forEach(function(row) {
const cells = [].slice.call(row.querySelectorAll('th, td'));
draggingColumnIndex > endColumnIndex
? cells[endColumnIndex].parentNode.insertBefore(cells[draggingColumnIndex], cells[endColumnIndex])
: cells[endColumnIndex].parentNode.insertBefore(cells[draggingColumnIndex], cells[endColumnIndex].nextSibling);
});
};
Вот полный код примера