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].
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].
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);
}
}
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
classNameproperty 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.
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.
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);
}
}
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);
}
}
| 1 | 2 | 3 |
//["A", "B", "C"]
//["D", "E", "F"]
//["G", "H", "I"]
//["1", "2", "3"]
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);
}
Written by Matt McDonald on Sunday, October 30th, 2011. Amended on Tuesday, November 1st, 2011.