CSS, HTML, JAVASCRIPT: 
HIGHLIGHTING TABLE ROWS UPON MOUSE OVERS

    
Below is an example of what we're trying to accomplish. Move the mouse over any row in the table and the row will be highlighted. 
 
Brand Dimensions Price Size Color  Type Comment
Row A  200x300 $200,000.00 small white  good 2 doors
Row B 200x100 $1,100,300.00 medium yellow good 3 wheels
Row C  1200x2100 $100,000.00 large white good 4 wheels
Row D 20x210 $300.00  medium blue  good 100 wheels
Row E 23x30 $2,300.00 large yellow good 9 wheels

Such highlighting is especially useful when you have a table that is wide and has a small font type. 

There are some specific goals in this exercise:

  1. Be able to preserve the original style of the table rows (including the cells within the row)

  2. To not depend on the initial state of the table.  This means that, we should not need require the table to follow any particular style (ie: there should not a be a requirement to change the table appearance attributes to be able to highlight/un-highlight it).

Because of those requirements, we cannot just change the style of  the row to be highlighted (using className or by altering style.backgroundColor, such as shown below):

<STYLE>
<!--
  tr { background-color: #DDDDDD}
  .initial { background-color: #DDDDDD; color:#000000 }
  .normal { background-color: #CCCCCC }
  .highlight { background-color: #8888FF }
//-->
</style>

<table border="0" cellspacing="0" bgcolor="#CCCCCC" cellpadding="0">
<tr>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Brand</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Dimensions</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Price</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Size</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Color</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>&nbsp;Type</b></td>
  <td bgcolor="#FFCC00" WIDTH="100"><b>Comment</b></td>
</tr>
<tr style="background-color:#CCCCCC;" 
  onMouseOver="this.className='highlight'" onMouseOut="this.className='normal'">
  <td>Row A</td>
  <td>200x300</td>
  <td>$200,000.00</td>
  <td>small</td>
  <td>white&nbsp;</td>
  <td>good</td>
<td>2 doors</td>
</tr>
<tr 
  onMouseOver="this.className='highlight'" onMouseOut="this.className='normal'">
  <td>Row B</td>
  <td>256x1000</td>
  <td>$232,300.00</td>
  <td>large</td>
  <td>yellow&nbsp;</td>
  <td>good</td>
  <td>nice</td>
</tr>
<tr class="initial" 
  onMouseOver="this.className='highlight'" onMouseOut="this.className='normal'">
  <td>Row 3</td>
  <td>543x300</td>
  <td>$122,111.00</td>
  <td>medium</td>
  <td>yellow&nbsp;</td>
  <td>good</td>
  <td>expensive</td>
</tr>
....

The above code uses className property to change the background color of the row.  The className is changed to 'highlight' when the mouse is moving over the table; and it's changed to 'normal' when the mouse is moving out.

This simple approach do work on some cases, but it has some serious limitations.  Here are some of them.

  • If a <tr> has its background color specified , that color will be lost after highlighting (for example Row B below).

  • If a style is already specified on a <tr> element, that <tr> will not highlight (for example, Row A below). 

  • If a <td> has its background color specified, that <td> will not be highlighted (for example, the last row of the table below).

  • <tr> style will be lost (for example, Row B, Row D below).

To see the problem, move the mouse around the table below.  Note: You might need to reload the page to see the problem.

Brand Dimensions Price Size Color Type Comment
Row A 200x300 $200,000.00 small white  good 2 doors
Row B 256x1000 $232,300.00 large yellow  good nice
Row C 543x300 $122,111.00 medium yellow  good expensive
Row D 56x340 $2,300.00 medium green good noisy
Row E 23x30 $2,300.00 large yellow good 9 wheels

Compare the behavior of this table with the table at the top of the page to see the problems. 

To resolve these problems, here are the things that need to be done:

  • Save the original state (eq: style) of the row and cells under the mouse.

  • Alter the background color of the row under the mouse.  This can be done using style sheet.

  • Restore the original background color of the row and cells.

While the options presented below are not perfect, and might not work as expected on all cases, they try to overcome the issues mentioned above.  

OPTION 1:
Using Mouse Event Handler on Every Row

 
In this option, we basically assign an onMouseOver event handler to every row in the table, and when the onMouseOver event is fired, we save the attributes (or style of the row AND the cells within the row).  We then highlight the row. 

When the user moves the mouse out of the row, the onMouseOut event is fired.  You'll see below that we also assign an onMouseOut event handler to un-highlight and restore the state of the row AND the cells within the row. 

The main drawback of this option is is that because there is no way to assign an event handler to a style sheet, the onMouseOver event handler will have to be explicitly assigned to every row on the table.  So the table row will look something like this:  

<TABLE onMouseOut="javascript:highlightTableRowVersionA(0);">
<!-- The onMouseOut above makes sure that when the mouse is moving -->
<!-- out of the table, no row is still highlighted-->

<tr onMouseOver="javascript:highlightTableRowVersionA(this, '#8888FF');">
  <td>Brand C</td>
  <td>1200x2100</td>
  <td>$100,000.00</td>
  <td>large</td>
  <td>white</td>
  <td>good</td>
  <td>4 wheels</td>
</tr>

<tr onMouseOver="javascript:highlightTableRowVersionA(this, '#8888FF');">
  <td>Brand C</td>
  <td>1200x2100</td>
  <td>$100,000.00</td>
  <td>large</td>
  <td>white</td>
  <td>good</td>
  <td>4 wheels</td>
</tr>

<tr onMouseOver="javascript:highlightTableRowVersionA(this, '#8888FF');">

<!-- AND SO ON -->

</TABLE>

See the example.

The onMouseOver event handler is added to every row.   As you can see, this can be quite a tedious task to add manually (what if there are over 100 rows?), unless the table is generated by a script that is.  

When the mouse is over a row, the function highlightTableRowVersionA(this) will be called.  In this function, the row background color will be altered (via style sheet).   The function highlightTableRowVersionA is shown below.  Notice also the parameter '#8888FF'.  This is the highlight color.  It is added as a parameter to make it easier to alter the highlight color between different tables. 

You will also see there, that I am assigning an event handler to the onMouseOut event like this:

tableRow.onMouseOut="highlightTableRowVersionA(0);";   

This is so that when the mouse is moving out of the row,  highlightTableRowVersionA(0) will be called, which in turn will un-highlight the row.   We know for sure once the mouse is enters a row, the mouse will eventually moves out of it.  So, assigning the onMouseOut like this saves time from having to assign it manually to every row. 

Now let's take a look at that function:

/////////////////////////////////////////////////////
// Highlight table row.
// myElement is the table row
// highlightColor is the color of the highlight
/////////////////////////////////////////////////////
function highlightTableRowVersionA(myElement, highlightColor)
{
  var i=0;
  // Restore color of the previously highlighted row
  for (i; i<savedStateCount; i++)
  {
    restoreBackgroundStyle(savedStates[i]); 
  }
  savedStateCount=0;

  // If you don't want a particular row to be highlighted, set it's id to "header"
  if (!myElement || (myElement && myElement.id && myElement.id=="header") )
    return;

  // The following code traverses every <td> within the <tr> and highlights it
  // by changing its style[backgroundColor] property
  if (myElement)
  {
    var tableRow=myElement;

    // Save the backgroundColor style OR the style class of the row, so we can restore
    //  it later
    if (tableRow)
    {
      savedStates[savedStateCount]=saveBackgroundStyle(tableRow);
      savedStateCount++;
    }

    // Since myElement is a <tr>, then find the first <td>.  
    // You'd think that the <td> is  going to be the 
    //   firstChild of the <tr>, but it's not always the case (it 
    //   depends on how the browser defines the DOM).
    var tableCell=findNode(myElement, "TD"); 

    i=0;
    // Loop through every sibling.  Theoretically, a sibling of a <td> should be 
    //  another <td>, but this is not always the case on certain browsers, 
    //  so we need to check the tagName to be sure and skip to the next 
    //  sibling if the sibling is not a <td>)
    // We then highlight every siblings
    while (tableCell)
    {
      // Make sure it's actually a cell (a <td>)
      if (tableCell.tagName=="TD")
      {
        // If no style has been assigned, assign it, otherwise Netscape will 
        // behave weird.
        if (!tableCell.style)
        {
          tableCell.style={};
        }
        else
        {
          savedStates[savedStateCount]=saveBackgroundStyle(tableCell); 
          savedStateCount++;
        }
        // Assign the highlight color
        tableCell.style["backgroundColor"]=highlightColor;

        // Optional: alter cursor
        tableCell.style.cursor='default';
        i++;
      }
      // Go to the next cell in the row
      tableCell=tableCell.nextSibling;
    }
  }
}

The code that actually changes the highlight color is marked in red.  As you see, this part is very simple.  The difficult task are:

  • Saving and restoring the original background color of the row and making sure that the code does not loose the original style of the row (if any).   This part is marked in green.  Notice that that there are some delicate maneuvers that needed to be done.    The two functions are shown below.  It's necessary to handle cases where a style is specified and when the background color is specified, therefore, we need to save both.  In short, dealing with styles using JavaScript is a fairly delicate task.  

/////////////////////////////////////////////////////
// This function takes an element as a parameter and 
// returns an object which contain the saved state
// of the element's background color.
/////////////////////////////////////////////////////
function saveBackgroundStyle(myElement)
{
  saved=new Object();
  saved.element=myElement;
  saved.className=myElement.className;
  saved.backgroundColor=myElement.style["backgroundColor"];
  return saved; 
}

/////////////////////////////////////////////////////
// This function takes an element as a parameter and 
// returns an object which contain the saved state
// of the element's background color.
/////////////////////////////////////////////////////
function restoreBackgroundStyle(savedState)
{
  savedState.element.style["backgroundColor"]=savedState.backgroundColor;
  if (savedState.className)
  {
    savedState.element.className=savedState.className; 
  }
}
  • Traversing the DOM tree of the row <tr> element to find the first table cell (ie: <td>).  This is done by the function findNode(), which is shown below.  You can see more on this subject here: Traversing DOM tree.   This function recursively traverses the table row until it finds the first <td>.
     

/////////////////////////////////////////////////////
// This function is used to find the first descendant with the specified tag name.
/////////////////////////////////////////////////////
function findNode(startingNode, tagName)
{
  // on Firefox, the <td> node might not be the firstChild node of the <tr> node
  myElement=startingNode;
  var i=0;
  while (myElement && 
    (!myElement.tagName || (myElement.tagName && myElement.tagName!=tagName)))
  {
    myElement=startingNode.childNodes[i++];
  } 
  if (myElement && myElement.tagName && myElement.tagName==tagName)
  {
    return myElement;
  }
  // On Internet Explorer, the <tr> node might be the firstChild node of the <tr> node 
  else if (startingNode.firstChild)
    return findNode(startingNode.firstChild, tagName);
  return 0;
}

In the highlightTableRowVersionA function, also notice this line: 

if (!myElement || (myElement && myElement.id && myElement.id=="header") )
    return;

This line enables certain rows to be omitted from highlighting.  Such rows should have its ID set to the string "header".  This is useful if you do not wish to highlight the table header.  

See the latest code: tableH.js
 

OPTION 2:
Using Mouse Event Handler and Row Detection


This option is only slightly different from the previous one, but it's also more extensible and reusable.  The gist of this method is to poll the mouse events within the <table> itself (as opposed to within every <tr> like in the previous solution).  The code then checks which row is currently below the mouse, and highlights the row.  

Advertisement

Below is an example of a table, on which I assign an onMouseOver event handler.  When the mouse is over the table (not just a row), the code check which row is under the mouse.  It then highlight the row (or the cell within the row).  I also assigned an onMouseOut event handler.  This is intended to un-highlight previously highlighted row if the user moves the mouse outside the table. 

<table onMouseOver="javascript:trackTableHighlight(event, '#8888FF');" 
  onMouseOut="javascript:highlightTableRow(0);">

<!-- TABLE CONTENT -->

</table>

See the example.

A major requirement of this code is the need to detect what element is currently below the mouse position.  In this example, the function trackTableHighlight(event) does this part. 
 I will not explain the detail of how this detection works here, but you can read about it on my other tutorial.).   Here's what trackTableHighlight() looks like.  
  

function trackTableHighlight(mEvent, highlightColor)
{
  if (!mEvent)
    mEvent=window.event;
		
  // Internet Explorer
  if (mEvent.srcElement)
  {
    highlightTableRow( mEvent.srcElement, highlightColor);
  }
  // Netscape and Firefox
  else if (mEvent.target)
  {
    highlightTableRow( mEvent.target, highlightColor);		
  }
}

And here's a slight modification to the highlightTableRow() function.  The major change from the previous version is that we need to make sure that we're getting the table row <tr> element.  This is because when the mouse is over a <table>, the mouse could be hovering on any kind of elements.  The mouse could be hovering over a <td> element, it could be over a <span> element, or an <img> element if the table has an image, or practically anything.  So we need to traverse the DOM tree until we got the table row <tr> element.  You can read more about this subject here: Traversing DOM treeThe changes are shown in blue.

 
/////////////////////////////////////////////////////
// Highlight table row.
// newElement could be any element nested inside the table
// highlightColor is the color of the highlight
/////////////////////////////////////////////////////
function highlightTableRow(myElement, highlightColor)
{
  var i=0;
  // Restore color of the previously highlighted row
  for (i; i<savedStateCount; i++)
  {
    restoreBackgroundStyle(savedStates[i]); 
  }
  savedStateCount=0;

  // To get the node to the row (ie: the <TR> element), 
  // we need to traverse the parent nodes until we get a row element (TR)
  // Netscape has a weird node (if the mouse is over a text object, 
  // then there's no tagName)
  while (myElement && 
    ((myElement.tagName && myElement.tagName!="TR") || !myElement.tagName))
  {
    myElement=myElement.parentNode;
  }
  if (!myElement || (myElement && myElement.id && myElement.id=="header") )
    return;

  if (myElement)
  {
    var tableRow=myElement;

    if (tableRow)
    {
      savedStates[savedStateCount]=saveBackgroundStyle(tableRow);
      savedStateCount++;
    }

    var tableCell=findNode(myElement, "TD"); 

    var i=0;
    while (tableCell)
    {
      if (tableCell.tagName=="TD")
      {
        if (!tableCell.style)
        {
           tableCell.style={};
        }
        else
        {
          savedStates[savedStateCount]=saveBackgroundStyle(tableCell); 
          savedStateCount++;
        }
        tableCell.style["backgroundColor"]=highlightColor;

        tableCell.style.cursor='default';
        i++;
      }
      tableCell=tableCell.nextSibling;
    }
  }
}

See the latest code: tableH.js 
 

<< NEXT PAGE >>


  
<< MORE TUTORIALS >>
(C)2005 F. Permadi
permadi@permadi.com

Terms of Use