Drag-and-Drop Column Reorder With Merged Cells: Why You Need a Visual Grid Map
TLDR
Reordering table columns containingcolspan merged cells fails when relying on raw DOM indices because cell nodes occupy multiple visual columns. Building a 2D VisualGridMapper uncouples visual coordinate positions from DOM elements, allowing drag handlers to resolve column insertion targets accurately.
| Extraction Strategy | Column Index Resolution | Handling Merged Spans | Drag Reorder Correctness |
|---|---|---|---|
DOM tr.cells Indexing | Positional DOM index (cellIndex) | Shifts incorrect row elements | Corrupts table structure |
2D VisualGridMapper | Visual $(x, y)$ coordinate map | Tracks isOrigin DOM flags | 100% Structural Precision |
Colspan cell spanning breaks index-based column reordering
In flat HTML tables without spanning, column $N$ maps directly to tr.cells[N]. However, when cells contain colspan="3", a single DOM element spans multiple visual grid positions. Naive drag-and-drop implementations that move tr.cells[N] shift incorrect elements on rows containing spanned headers.
To reorder columns safely without corrupting table layout, column coordinates must be calculated independently of DOM element indices.
Cartesian coordinates isolate DOM changes from cell spans
We implemented a VisualGridMapper that scans the table matrix and builds a 2D spatial representation. Each cell stores its underlying element reference and an isOrigin boolean:
// Move column using 2D grid coordinates and origin tracking
export function moveColumnWithGridMap(mapper, fromIndex, toIndex) {
const movedElements = new Set();
for (let r = 0; r < mapper.maxRows; r++) { const cellData = mapper.grid[r][fromIndex];
// Only move origin DOM elements once per spanned row if (cellData && cellData.isOrigin && !movedElements.has(cellData.element)) { movedElements.add(cellData.element); const movingCell = cellData.element; const targetData = mapper.grid[r][toIndex];
if (targetData && targetData.isOrigin) { targetData.element.before(movingCell); } } } }
By tracking isOrigin: true flags, the reordering function moves spanned DOM elements exactly once per row, preventing duplicate insertion bugs during drag operations.
Table Layout with Colspan:
Visual Col: 0 1 2 3
+------+------+------+------+
Row 0: | cellIndex 0 (colspan=3) | cellIndex 1
| [A] | [B]
+------+------+------+------+
Row 1: | cellIndex 0 | cellIndex 1 | cellIndex 2 | cellIndex 3
| [C] | [D] | [E] | [F]
+------+------+------+------+
VisualGridMapper Representation: Row 0: [A (Origin), A (Ref), A (Ref), B (Origin)] Row 1: [C (Origin), D (Origin), E (Origin), F (Origin)]
Rule of thumb: Map HTML tables to a 2D Cartesian grid to calculate visual column positions independently of DOM cell counts.