Replacing Table Cells with the DOM

  1. Introduction
  2. Removing Cells with deleteCell(index)
    1. Warning
    2. Reflowing and Repainting
    3. Warning
  3. Appending Cells with insertCell(index)
    1. Warning
    2. Warning
  4. Demo
    1. Code
  5. Citations
  6. Footnotes
    1. Attributions

Introduction

When interfacing with tables (HTMLTableElement [0]) via the DOM, a comprehensive set of methods and properties is provided. In this article, HTMLTableRowElement::insertCell(index) [1] and HTMLTableRowElement::deleteCell(index) [2] will be used in conjunction with HTMLTableRowElement::cells [3].


Removing cells with deleteCell(index)

Clearing a table row's cells can be done properly in a few ways. One such way is via the aforementioned deleteCell(index) method. Its index parameter accepts a (whole) Number. If a value of -1 is passed, the last cell in the specified table row will be removed. If a value less than the number of cells in the corresponding table row and greater than or equal to 0 is passed, the table cell that reflects the index parameter will be removed. If the (whole) Number passed is greater than or equal to the number of cells in the corresponding table row, or is a negative number other than -1, a DOMException entitled INDEX_SIZE_ERR is supposed be raised. The ability to pass a negative index was introduced in the DOM Level 2 Spec [4].

Warning:

Some MSHTML (IE) builds will throw an Error with a message of "Invalid Argument" instead of a DOMException entitled INDEX_SIZE_ERR. Take caution.

For consistency while using table methods, this is a possible approach. Here is an example:

function clearCells(row)
{
	while(row.cells[0])
	{
		row.deleteRow(row.cells.length - 1);
	}
}

Another method is to simply use a method that clears a node's childNodes. Here is an example:

function clearChildNodes(node)
{
	//add nodeType check if desired
	while(node.firstChild)
	{
		node.removeChild(node.firstChild);
	}
}

Reflowing and Repainting

However, removing every single cell, then appending replacement cells can cause a browser to reflow. A "reflow" is when a browser has adjusted the flow of elements because the document flow has changed. Reflows are synchronous and can eat up CPU. To minimize the amount of reflows, the childNodes of the cells themselves can be modified instead of removing and appending new cells.

Opera's "dev" site has this [5] to say on reflows:

A reflow is a more significant change. This will happen whenever the DOM tree is manipulated, whenever a style is changed that affects the layout, whenever the className property of an element is changed, or whenever the browser window size is changed. The engine must then reflow the relevant element to work out where the various parts of it should now be displayed. Its children will also be reflowed to take the new layout of their parent into account. Elements that appear after the element in the DOM will also be reflowed to calculate their new layout, as they may have been moved by the initial reflows. Ancestor elements will also reflow, to account for the changes in size of their children. Finally, everything is repainted.

Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.

Here is an example that utilizes the same cells (used in the demo below):

function getFirstWholeTextNode(node)
{

	var i = 0, nodes = node.childNodes,
	limit = nodes.length,
	whitespace = /^\s+$/;
	for(i;i<limit;i++)
	{
		if(nodes[i].nodeType === 3)
		{
			if(!whitespace.test(nodes[i].data))
			{
				return nodes[i];
			}
		}
	}
	return null;
}

function replaceRowContent(items, row)
{
	var i = 0, limit = items.length,
	cell, text;
	for(i;i<limit;i++)
	{
		cell = row.cells[i];
		text = getFirstWholeTextNode(cell);
		if(text)
		{
			text.data = items[i];
		}
	}
}

Since only the textual content is modified (instead of the cells being removed & appended), the table cells only need to be "repainted", or redrawn.

Of course, mileage may (and likely will) vary. Keep in mind that different contexts call for different methods. Write a method that fits the current context best.


Appending Cells with insertCell(index)

Similarly, inserting cells can be done via the insertCell(index) method. The index parameter takes a (whole) Number as valid input. If -1 is passed as the parameter, a blank cell (if supported) will be appended to the end of the table row. If the (whole) Number passed is less than or equal to the number of cells in the corresponding table row or greater than or equal to 0, the blank cell created will be appended to the matching index in the table row. If the (whole) Number passed is greater than the number of cells in the corresponding table row, or is a negative number other than -1, a DOMException entitled INDEX_SIZE_ERR is supposed be raised. The ability to pass a negative index was introduced in the DOM Level 2 Spec. Upon successful insertion, insertCell(index) is supposed to return a blank table cell that was inserted into the table.

Warning:

Some MSHTML (IE) builds will throw an Error with a message of "Invalid Argument" instead of a DOMException entitled INDEX_SIZE_ERR. Upon an unsuccessful insertion, insertCell(index) is expected to return null. Take caution.

Here is an example using insertCell(index):

function appendRowContent(items, row)
{
	var currentCell = 0, limit = items.length,
	cell, text;
	//global being a reference to the "this" value/global object
	for(currentCell;currentCell<limit;currentCell++)
	{
		cell = row.insertCell(row.cells.length - 1);
		text = global.document.createTextNode(items[currentCell]);
		cell.appendChild(text);
	}
}

Warning:

In MSHTML (IE), usage of the index parameter is expected to not exceed the length of HTMLTableRowElement::cells minus 1 [6]. Be cautious when using this approach, especially with older or incomplete browsers.

Alternatively, Document.createElement(tagName) [7] can be used to create table cells. In browsers that don't implement insertCell(index) properly, this is the preferred approach. Here's an example:

function appendRowContent(items, row)
{
	var currentCell = 0, limit = items.length,
	cell, text;
	//global being a reference to the "this" value/global object
	for(currentCell;currentCell<limit;currentCell++)
	{
		/*
			Note the following table section—table cell relationships:
			thead—th
			tbody—td
			tfoot—tf
		*/
		cell = document.createElement("td");
		text = global.document.createTextNode(items[currentCell]);
		cell.appendChild(text);
		row.appendChild(cell);
	}
}

Demo:

1 2 3

Warning:

JavaScript is required to run these results.
//["A", "B", "C"]
//["D", "E", "F"]
//["G", "H", "I"]
//["1", "2", "3"]

Code:

Full Code
function getFirstWholeTextNode(node)
{
	var i = 0, nodes = node.childNodes,
	limit = nodes.length,
	whitespace = /^\s+$/;
	for(i;i<limit;i++)
	{
		if(nodes[i].nodeType === 3)
		{
			if(!whitespace.test(nodes[i].data))
			{
				return nodes[i];
			}
		}
	}
	return null;
}

function replaceRowContent(items, row)
{
	var i = 0, limit = items.length,
	cell, text;
	for(i;i<limit;i++)
	{
		cell = row.cells[i];
		text = getFirstWholeTextNode(cell);
		if(text)
		{
			text.data = items[i];
		}
	}
}
function runResultA()
{
	//["A", "B", "C"]
	replaceRowContent(resultA, elements.demoRow);
}

function runResultB()
{
	//["D", "E", "F"]
	replaceRowContent(resultB, elements.demoRow);
}

function runResultC()
{
	//["G", "H", "I"]
	replaceRowContent(resultC, elements.demoRow);
}

function runResultInitial()
{
	//["1", "2", "3"]
	replaceRowContent(resultInitial, elements.demoRow);
}

Citations:

[0]
HTMLTableElement
[1]
HTMLTableRowElement::insertCell
[2]
HTMLTableRowElement::deleteCell
[3]
HTMLTableRowElement::cells
[4]
DOM Level 2 Spec
[5]
dev.opera.com on Reflows
[6]
HTMLTableRowElement::insertCell on MSDN
[7]
Document.createElement

Footnotes

Written by Matt McDonald on Sunday, October 30th, 2011. Amended on Tuesday, November 1st, 2011.

Attributions

Dr. J R Stockton
CSS & Verbiage

Valid HTML 4.01 Strict!

Valid CSS!