Helpful Information
 
 
Category: DOM and JSON scripting
Table row filtering

Hey all

Here's the setup. I've got a PHP page that delivers MySql data into an HTML table for a To Do list. Works peachy. Now, I wanted to add a filtering tool to the table, which I've also done, and it works fine. However, I'm dissatisfied with some of the methods I've used becuase they involve a bit of hard coding, and will make it difficult to expand on later down the road. I think you will see what I mean. There's a decent amount of code, but I want to include it all so that you may just copy>paste it and see the page as I do. Also, I thought I was using strictly DOM methods, but this isn't working in NS6 :(

Thanks to those who have the patience for this.
<!doctype html public "-//W3C//DTD HTML 3.2 Final//EN" />
<html>
<head>
<title>Dynamic Table Test</title>
<style>
td {
font-size: 12px;
}
h2, h3 {
font-family: Verdana, Arial, Helvetica, sans-serif;
color: #696969;
}
table.dispTable {
font-family: Verdana, sans-serif;
font-size: 10px;
border: 1px solid black;
width: 585px;
padding: 0px;
margin: 5px 0px;
}
table.dispTable th {
color: white;
font-weight: bold;
font-size: 120%;
background-color: #999;
padding: 3px;
text-align: left;
}
table.dispTable tr td {
padding: 3px;
}
table.dispTable tr.odd {
background-color: #FFF;
}
table.dispTable tr.even {
background-color: #EEE;
}
</style>
<script>
function doFilter(t, val, cIndex) {
var cIndex2 = (cIndex == 1) ? 2 : 1;
var sel2 = t.childNodes[1].cells[cIndex2].firstChild;
var val2 = sel2.options[sel2.selectedIndex].value;

for (var i=2; i<t.childNodes.length; i++) {
var tr = t.childNodes[i]
var txtNode1 = tr.cells[cIndex].firstChild.data;
var txtNode2 = tr.cells[cIndex2].firstChild.data;
if ((txtNode1 == val || val == 'all') && (txtNode2 == val2 || val2 == 'all'))
tr.style.display = "";
else
tr.style.display = "none";
}
}
</script>
</head>

<body>

<h3>Jon's To Do List</h3>
<table class="dispTable" cellspacing="0">
<tr id="1">
<th>Title</th>
<th>Priority</th>
<th>Status</th>
<th>Date</th>
</tr>
<tr id="2">
<td style="text-align:right">Filter:</td>
<td>
<select id="fil_priority" onChange="doFilter(this.parentNode.parentNode.parentNode,this.options[this.selectedIndex].value, this.parentNode.cellIndex);">
<option value="all">All</option>
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
<option value="Critical">Critical</option>
<option value="N/A">N/A</option>
</select>
</td>
<td>
<select id="fil_status" onChange="doFilter(this.parentNode.parentNode.parentNode,this.options[this.selectedIndex].value, this.parentNode.cellIndex);">
<option value="all">All</option>
<option value="Open">Open</option>
<option value="Deferred">Deferred</option>
<option value="Complete">Complete</option>
<option value="Archived">Archived</option>
<option value="Prepaid">Prepaid</option>
</select>
</td>
<td>Date</td>
</tr>
<tr id="3" class="odd">
<td>Test #1</td>
<td>Medium</td>
<td>Open</td>
<td>8/12/02</td>

</tr>
<tr id="4" class="even">
<td>Test #2</td>
<td>High</td>
<td>Complete</td>
<td>8/12/02</td>

</tr>
<tr id="5" class="odd">
<td>Fix Servers</td>
<td>High</td>
<td>Complete</td>
<td>8/13/02</td>
</tr>
<tr id="6" class="even">
<td>Clean House</td>
<td>Critical</td>
<td>Complete</td>
<td>8/16/02</td>

</tr>
<tr id="7" class="odd">
<td>another test</td>
<td>Critical</td>
<td>Open</td>
<td>8/16/02</td>

</tr>
<tr id="8" class="even">
<td>Test Again!!!</td>
<td>Medium</td>
<td>Open</td>
<td>8/16/02</td>
</tr>
</table>
</body>
</html>

if you do this:

...
<tr>
<td>stuff</td>
</tr>
...

Gecko browsers will interpret it like this:
RowElement
-text Node
-TD Element
-text Node

So you your TD is no longer first child....
You have 3 options:
1. have table constructed without any new lines and spaces between tags.
2. have a function that will parse your document and remove all the empty text nodes on load
3. construct the table on the client side (see my page for example)

Just going a little further on the concept of "spurious" text nodes:

Whitespace in Mozilla's XML parser is interpretted as a TextNode to preserve formatting. It is perfectly legal to interpret indentation as text nodes, and in some cases is very useful. When IE parses your code, its internal representation is an ugly, unreadable mess. Just look at document.documentElement.outerHTML, and you'll see what I mean. Mozilla will leave formatting more or less the way it is.

The negative aspect though is that all of a sudden, you have text nodes where you don't want them.

An excellent way around this is the DOM2 Traversal specs, in which you can specify "filters" that only iterate through nodes that match the filter, i.e. node.nodeType == Node.ELEMENT_NODE

Well, I coded up this function to remove all the empty textNodes, but IT doesn't work in NS :confused:
function detn(oNode) {
for (var j=0; j<oNode.childNodes.length; j++) {
var currNode = oNode.childNodes[j];
if (currNode.nodeType == 3 && (currNode.data == '' || currNode.data == ' ')) {
alert('Empty Text Node Found');
currNode.removeNode();
}
if (currNode.childNodes.length>0)
detn(currNode);
}
}
jkd
An excellent way around this is the DOM2 Traversal specs Perhaps, but not so excellent when over 90% of my demographic on this projects uses IE.

I think it should be
node.nodeValue (as defined in W3C specs) not
node.data

Thanks Vladdy, I changed it to nodeValue, but I still don't get even a single alert in NS

OK,
I took another look at your function...

First, you can not use for loop when you remove nodes you are looping through,
use this:


var j=0;
while( j<oNode.childNodes.length)
{ var currNode = oNode.childNodes[j];
//do things
j++;
}


Second, you are using the removeNode method incorrectly. Do this:


oNode.removeNode(currNode);


And third, you are testing for empty strings and space when your nodes have new lines and such. Try this:

if (currNode.nodeType == 3 && escape(currNode.nodeValue).substring(0,1) == '%')


Now, your DoFilter still does not work in Gecko....
I think the problem is that Gecko interprets the table structure differently (especially since you do not have <tbody> and <thead> defined) and your
this.parentNode.parentNode.parentNode
ends up referencing different node.
Put the rows that you are working with in
<tbody id="tablebody'>
and try this:
DoFilter(document.getElementById('tablebody', ...)

Ok, I've got this now, but it doesn't loop in NS (or remove the child, I suspect. Why does my highlighted line work in NS?
function detn(oNode) {
for (var i=0; i<oNode.childNodes.length;i++) {
var currNode = oNode.childNodes[i];
if (currNode.childNodes.length>0)
detn(currNode);
if (currNode.nodeType == 3 && !currNode.nodeValue.search(/^\s+$/)) {
alert('Empty Text Node Found');
oNode.removeChild(currNode);
}
}
}

Ok, I'll try that stuff and let you know....

Well, through some various tests, I've discovered that my recursive function isn't recurring properly in NS, and at the moment I'm not sure why. calling detn() the first time on the TBODY node yeilds the removal of 9 empty text nodes. If I force run detn() again (on the TBODY) it removes an additional 40 empty text nodes. One more iteration that removes 4 emtpy text nodes is required before no more are found, and all my doFilter() functions work properly.

Any clues? I'm gonna keep messing with it.

Here is how my detn() function looks at the moment
function detn(oNode) {
var j=0;
while( j<oNode.childNodes.length) {
var currNode = oNode.childNodes[j];
if (currNode.childNodes.length>0)
detn(currNode);
if (currNode.nodeType == 3 && !currNode.nodeValue.search(/^\s+$/)) {
// alert('Empty Text Node Found');
oNode.removeChild(currNode);
}
j++;
}
}

function removeAllTextNodes(pNode) {
var tree = document.createTreeWalker(pNode, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { return /\S/.test(node.nodeValue) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT } }, false);
while (tree.nextNode())
tree.currentNode.parentNode.removeChild(tree.currentNode);
}


I create a TreeWalker with the root node set to the argument. I then tell it that only TextNodes will be visible in iterating, I then create a filter which rejects all text nodes that /\S/.test returns true for (i.e. non-whitespace), and accept any node which only has whitespace (i.e. indentation).
I then iterate through all the nodes matching my criteria, and remove them from their parent node.

This should take care of your spurious text node problem in Mozilla.

Originally posted by jkd
This should take care of your spurious text node problem in Mozilla. *Should* being the operative word :D I appreciate your time jkd, but it's not working.

After much deliberation, I've ended with this, which works.
function initTNparse() {
window.TNcount = 1;
while (window.TNcount != 0) {
window.TNcount = 0;
TNparse(document.getElementById('tablebody'));
}
}

function TNparse(oNode) {
var j=0;
while( j<oNode.childNodes.length) {
var currNode = oNode.childNodes[j];
if (currNode.childNodes.length>0)
TNparse(currNode);
if (currNode.nodeType == 3 && !currNode.nodeValue.search(/^\s+$/)) {
oNode.removeChild(currNode);
window.TNcount++;
}
j++;
}
}

</script>
</head>

<body onLoad="initTNparse();">Whew, what a day! Now, I'm still curious if I can get my doFilter() function to be any more flexible. Right now it's hard coded to work with only two of the columns. There may be occaisions where I need more. Any ideas?

Building table dynamically would be my answer not only to the differencies in the way Gecko and IE parse HTML but to flexibility as well.










privacy (GDPR)