Engineering Journal
Table Formatter
Table Formatter

Drag-and-Drop Column Reorder With Merged Cells: Why You Need a Visual Grid Map

2026-06-04

TLDR

Reordering table columns containing colspan 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 StrategyColumn Index ResolutionHandling Merged SpansDrag Reorder Correctness
DOM tr.cells IndexingPositional DOM index (cellIndex)Shifts incorrect row elementsCorrupts table structure
2D VisualGridMapperVisual $(x, y)$ coordinate mapTracks isOrigin DOM flags100% 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.
Read this post in the full Engineering Journal →