Click here to Skip to main content
Licence CPOL
First Posted 24 Jun 2006
Views 148,072
Downloads 2,806
Bookmarked 47 times

Fixed headers in large HTML tables

By | 24 Jun 2006 | Article
There are quite a lot of ways to fix the header column and rows in HTML tables. But when tables become larger, most of them are not useful because scrolling gets far too slow. In this article, I will show an applicable way for IE.

Introduction

There are quite a lot of ways to fix the header column and rows in HTML tables. But when tables become larger, most of them are not useful because scrolling gets far too slow. In the following sample, I will show an applicable way for IE.

My HTML page contains two divs and a table.

...
<div id="outerDiv">
 <div id="innerDiv">
  <table>
   ...
  </table>
 </div>
</div>
...

In my sample, the table looks like this (the red border shows the innerDiv):

Sample screenshot

The main idea is to copy the innerDiv with the table three times so that there is a div each for the header row, the header column, the first cell in the header row, and the body of the table.

  • In the first three divs, overflow must be set to hidden.
  • In the body div, overflow can be set to scroll if the body is larger then the available space. Furthermore, the table in the body div needs to be positioned absolutely. Top and Left positions have to be negative (Top = -height of header row, Left = -width of header column) so that the headers are no more visible than the body div.

By copying the whole table, all rows and columns will have equal width and height. If you would only copy the first row of the table to the header div, column width in the header could differ from the body columns. After copying the divs, my outerDiv contains four innerDivs (the red borders show the innerDivs):

Sample screenshot

Finally, the divs have to scroll synchronously. When you scroll to the right, the header row has to move to the right too, and when you scroll to the bottom, the header column needs to move too. I found a nice way to do this here.

Sample screenshot

You can view an online demo here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Karin Huber

Software Developer
software architects
Austria Austria

Member

Hi, my name is Karin Huber. Since 1998 I have been working as a developer and IT consultant focusing on building database oriented web applications. In 2007 my friend Rainer and I decided that we want to build a business based on COTS (component off-the-shelf) software. As a result we founded "software architects".
 
These days we are offering our first version of the time tracking software called 'time cockpit'. You can find more information at www.timecockpit.com.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionFix mutliple columns PinmemberMember 897905823:50 24 May '12  
BugERROR PinmemberBudi Arsana7:13 24 May '12  
QuestionCan u provide me code for fixed header a Pinmemberanandkbpalle17:06 14 Feb '12  
QuestionMy "CreateScrollHeader" is called only from page "reload" function in code, not from "refresh" button in browser PinmemberMember 80271315:27 23 Jun '11  
Answer"iframe" and "frame" are the reason. PinmemberMember 80271315:14 29 Jun '11  
GeneralRe: But "search" and "checkbox" are not working PinmemberMember 80271315:17 29 Jun '11  
QuestionHow to set width & heigth PinmemberMember 77987295:15 30 Mar '11  
AnswerRe: How to set width & heigth PinmemberMember 779872921:25 30 Mar '11  
GeneralNice Work PinmemberShahib71310:17 30 Sep '10  
GeneralMy vote of 4 Pinmembermanas sinha19:44 15 Jul '10  
GeneralSupport for Firefox and Chrome PinmemberArne Møller4:51 4 Aug '09  
GeneralRe: Support for Firefox and Chrome [modified] PinmemberKeith Worden4:56 9 Jul '10  
GeneralFreezing multiple columns PinPopularmemberSøren Christensen22:02 22 Apr '09  
Hi
 
I've made a version of the script that supports freezing multiple columns.
 
The HTML should look like this:
 
<pre>
<div id="outerDiv">
   <div id="innerDiv">
      <table>
         <thead>
     <tr>
        <th>Table</th>
        <th>Column 1</th>
        <th>Column 2</th>
        <th>Column 3</th>
        <th>Column 4</th>
        <th>Column 5</th>
        <th>Column 6</th>
        <th>Column 7</th>
        <th>Column 8</th>
        <th>Column 9</th>
        <th>Column 10</th>
        <th>Column 11</th>
        <th>Column 12</th>
     </tr>
         </thead>
         <tbody>
     <tr>
        <th>Row 1</th>
        <th>Value 1</th>
        <td>Value 2</td>
        <td>Value 3</td>
        <td>Value 4</td>
        <td>Value 5</td>
        <td>Value 6</td>
        <td>Value 7</td>
        <td>Value 8</td>
        <td>Value 9</td>
        <td>Value 10</td>
        <td>Value 11</td>
        <td>Value 12</td>
     </tr>
...
</pre>
 
All cells wrapped in 'th' will be fixed.
 
The script (Based on Kenneths version):
 
<pre>
var divContent = null;
var divHeaderRow = null;
var divHeaderColumn = null;
var divHeaderRowColumn = null;
var fixedColumnWidth;
var x;
var y;
var horizontal = false;
var vertical = false;
 
// Copy table to top and to left
function CreateScrollHeader(content, scrollHorizontal, scrollVertical)
{
      horizontal = scrollHorizontal;
      vertical = scrollVertical;
      divContent = content;
     
      if(BrowserDetect.browser == "Konqueror") {
               window.onload = CreateScrollHeaderActual;     
      } else {
               CreateScrollHeaderActual();
      }     
}
 
function CreateScrollHeaderActual() {
      if (divContent != null)
      {  
               var widthCosmeticAdd = 0;
               var heightCosmeticAdd = 0;
 
               // browser specific cosmetic tweaks     
               if (BrowserDetect.browser == "Explorer") {
                     widthCosmeticAdd = 2;
                     heightCosmeticAdd = 2;
               } else if(BrowserDetect.browser == "Opera" || BrowserDetect.browser == "Konqueror" || BrowserDetect.browser == "Safari") {
                     widthCosmeticAdd = 1;
                     heightCosmeticAdd = 1;           
               }     
           
               var originalTable = findFirstElementByType(divContent, 'table');
               var headerRow = findFirstElementByType(originalTable, 'thead');
 

             //Find the no of fixed colums
             var firstRow = findFirstElementByType(findFirstElementByType(originalTable, 'tbody'),'tr');
             var noOfFixedCols = firstRow.getElementsByTagName('th').length;
             //Calculate the total width of the fixed columns
             var headerRowTR = findFirstElementByType(headerRow,'tr');
             fixedColumnWidth = 0;
             for( i = 0 ; i < noOfFixedCols ; i++ )               {
                  fixedColumnWidth = fixedColumnWidth + headerRow.getElementsByTagName('th')[i].offsetWidth;         
             }
            
               x = originalTable.offsetWidth;
               y = originalTable.offsetHeight;           
               //Clone the entire table - including innerDiv
               divHeaderRow = divContent.cloneNode(true);  
               //Should the header row remain fixed?
               if (horizontal)
               {
                       //Set the height of the innerDiv div
                     divHeaderRow.style.height = (headerRow.offsetHeight + heightCosmeticAdd) + 'px';
                     //Make sure no scrollbars are visible in the new header row
                     divHeaderRow.style.overflow = "hidden";
                     //Insert the copy of innerDiv before the existing innerDiv
                     divContent.parentNode.insertBefore(divHeaderRow, divContent);
                     //Set position type to absolute - in order to subtract the 'new' header row
                     originalTable.style.position = "absolute";
                     //Make sure the header row is not visible on the original table
                     originalTable.style.top = "-" + (headerRow.offsetHeight + heightCosmeticAdd) + 'px';
                    
                     y = y - headerRow.offsetHeight;
               }
             //Clone the div just created abowe
               divHeaderRowColumn = divHeaderRow.cloneNode(true);                    
               //Clone the original innerDiv
               divHeaderColumn = divContent.cloneNode(true);
              
               divContent.style.position = "relative";
 
               if (vertical)
               {
                       //Insert the fixed column before orginal table
                     divContent.parentNode.insertBefore(divHeaderColumn, divContent);
                     //Push the the orginal innerDiv right
                     divContent.style.left = fixedColumnWidth + 'px';
 
                     originalTable.style.position = "absolute";
                     //Make sure the first column isn't visiable in the orginal table
                     originalTable.style.left = "-" + fixedColumnWidth + 'px';
               }
               else
               {
                     divContent.style.left = "0px";
               }
 
               if (vertical)
               {
                       //Set the width of the fixed column
                     divHeaderColumn.style.width = (fixedColumnWidth + widthCosmeticAdd) + 'px';
                     //Make sure scrollbars are hidden
                     divHeaderColumn.style.overflow = "hidden";
                     //Set the zIndex to make sure this layer is above the orginal table
                     divHeaderColumn.style.zIndex = "99";
                    
                     divHeaderColumn.style.position = "absolute";
                     divHeaderColumn.style.left = "0px";
                     //Move the column down - so that it starts below the header row
                     divHeaderColumn.style.top = (headerRow.offsetHeight + heightCosmeticAdd) + "px";
                     //Make sure the that when the user scoll down through the content - then the fixed column also scolls
                     addScrollSynchronization(divHeaderColumn, divContent, "vertical");
                     x = x - fixedColumnWidth;
               }
 
               if (horizontal)
               {
                     if (vertical)
                     {                              
                              divContent.parentNode.insertBefore(divHeaderRowColumn, divContent);
                     }
                     divHeaderRowColumn.style.position = "absolute";
                     divHeaderRowColumn.style.left = "0px";
                     divHeaderRowColumn.style.top = "0px";
                     divHeaderRowColumn.style.width = (fixedColumnWidth + widthCosmeticAdd) + 'px';
                     divHeaderRowColumn.overflow = "hidden";
                     divHeaderRowColumn.style.zIndex = "100";
                     divHeaderRowColumn.style.backgroundColor = "#ffffff";
                    
               }
           
               if (horizontal)
               {
                     addScrollSynchronization(divHeaderRow, divContent, "horizontal");
               }
 
               if (horizontal || vertical)
               {
                     window.onresize = ResizeScrollArea;
                     ResizeScrollArea();
               }
      }
}
 
function findFirstElementByType(startNode, search) {
      if (! startNode.hasChildNodes()) return null;
     
      var children = startNode.childNodes;
      var i;
      for (i = 0; i < children.length; i ++) {
               if (children[i].nodeName.toUpperCase() == search.toUpperCase()) {
                     return children[i];
               }
      }
}
 
function findPosY(obj)
   {
      var curtop = 0;
      if(obj.offsetParent)
            while(1)
            {
               curtop += obj.offsetTop;
               if(!obj.offsetParent)
                  break;
               obj = obj.offsetParent;
            }
      else if(obj.y)
            curtop += obj.y;
      return curtop;
   }
 
// Resize scroll area to window size.
function ResizeScrollArea()
{
      var height = getInnerHeight() - findPosY(divHeaderRow) - 75;
 
      if (!vertical)
      {
               height -= divHeaderRow.offsetHeight;
      }
      var width = getInnerWidth() - 50;
      if (!horizontal)
      {
               width -= divHeaderColumn.offsetWidth;
      }
      var headerRowsWidth = 0;
 
      if (divHeaderRowColumn != null)
      {
               headerRowsWidth = divHeaderRowColumn.offsetWidth;
      }
 
      // width
      if (findFirstElementByType(divContent, 'table').offsetWidth > width)
      {
               divContent.style.width = Math.max(width - headerRowsWidth, 0) + 'px';
               divContent.style.overflowX = "scroll";
               divContent.style.overflowY = "auto";
      }
      else
      {
               divContent.style.width = x + 'px';
               divContent.style.overflowX = "auto";
               divContent.style.overflowY = "auto";
      }
 
      if (divHeaderRow != null)
      {
               divHeaderRow.style.width = (divContent.offsetWidth + headerRowsWidth) + 'px';
      }
 
      // height
      if (findFirstElementByType(divContent, 'table').offsetHeight > height)
      {
               divContent.style.height = Math.max(height, 80) + 'px';
               divContent.style.overflowY = "scroll";
      }
      else
      {
               divContent.style.height = y + 'px';
               divContent.style.overflowY = "hidden";
      }
      if (divHeaderColumn != null)
      {
               divHeaderColumn.style.height = divContent.offsetHeight + 'px';
      }
 
      // check scrollbars
      if (divContent.style.overflowY == "scroll")
      {
               divContent.style.width = (divContent.offsetWidth + 17) + 'px';
      }
      if (divContent.style.overflowX == "scroll")
      {
               divContent.style.height = (divContent.offsetHeight + 17) + 'px';
      }
 
      divContent.parentNode.style.width = (divContent.offsetWidth + headerRowsWidth) + 'px';
}
 
// next two functions from quirksmode.org
 
function getInnerHeight() {
      var y;
      if (self.innerHeight) // all except Explorer
      {
               y = self.innerHeight;
      }
      else if (document.documentElement && document.documentElement.clientHeight)
               // Explorer 6 Strict Mode
      {
               y = document.documentElement.clientHeight;
      }
      else if (document.body) // other Explorers
      {
               y = document.body.clientHeight;
      }
      return y;
}
 
function getInnerWidth() {
      var x;
      if (self.innerWidth) // all except Explorer
      {
               x = self.innerWidth;
      }
      else if (document.documentElement && document.documentElement.clientWidth)
               // Explorer 6 Strict Mode
      {
               x = document.documentElement.clientWidth;
      }
      else if (document.body) // other Explorers
      {
               x = document.body.clientWidth;
      }
      return x;
}
 

// ********************************************************************************
// Synchronize div elements when scrolling
// from http://webfx.eae.net/dhtml/syncscroll/syncscroll.html
// ********************************************************************************
// This is a function that returns a function that is used
// in the event listener
function getOnScrollFunction(oElement, srcElement) {
      return function () {
               if (oElement._scrollSyncDirection == "horizontal" || oElement._scrollSyncDirection == "both")
                     oElement.scrollLeft = srcElement.scrollLeft;
               if (oElement._scrollSyncDirection == "vertical" || oElement._scrollSyncDirection == "both")
                     oElement.scrollTop = srcElement.scrollTop;
      };
 
}
 
// This function adds scroll syncronization for the fromElement to the toElement
// this means that the fromElement will be updated when the toElement is scrolled
function addScrollSynchronization(fromElement, toElement, direction) {
      removeScrollSynchronization(fromElement);
     
      fromElement._syncScroll = getOnScrollFunction(fromElement, toElement);
      fromElement._scrollSyncDirection = direction;
      fromElement._syncTo = toElement;
      if (toElement.addEventListener) {
               toElement.addEventListener("scroll", fromElement._syncScroll, false);
      } else {
               toElement.attachEvent("onscroll", fromElement._syncScroll);
      }
}
 
// removes the scroll synchronization for an element
function removeScrollSynchronization(fromElement) {
      if (fromElement._syncTo != null) {
               if (fromElement._syncTo.removeEventListener) {
                     fromElement._syncTo.removeEventListener("scroll", fromElement._syncScroll, false);
               } else {
                     fromElement._syncTo.detachEvent("onscroll", fromElement._syncScroll);
               }
      }
 
      fromElement._syncTo = null;
      fromElement._syncScroll = null;
      fromElement._scrollSyncDirection = null;
}
 
// browser detection routines from quirksmode.org
var BrowserDetect = {
      init: function () {
               this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
               this.version = this.searchVersion(navigator.userAgent)
                     || this.searchVersion(navigator.appVersion)
                     || "an unknown version";
               this.OS = this.searchString(this.dataOS) || "an unknown OS";
      },
      searchString: function (data) {
               for (var i=0;i<data.length;i++)      {
                     var dataString = data[i].string;
                     var dataProp = data[i].prop;
                     this.versionSearchString = data[i].versionSearch || data[i].identity;
                     if (dataString) {
                              if (dataString.indexOf(data[i].subString) != -1)
                                    return data[i].identity;
                     }
                     else if (dataProp)
                              return data[i].identity;
               }
      },
      searchVersion: function (dataString) {
               var index = dataString.indexOf(this.versionSearchString);
               if (index == -1) return;
               return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
      },
      dataBrowser: [
               {         string: navigator.userAgent,
                     subString: "OmniWeb",
                     versionSearch: "OmniWeb/",
                     identity: "OmniWeb"
               },
               {
                     string: navigator.vendor,
                     subString: "Apple",
                     identity: "Safari"
               },
               {
                     prop: window.opera,
                     identity: "Opera"
               },
               {
                     string: navigator.vendor,
                     subString: "iCab",
                     identity: "iCab"
               },
               {
                     string: navigator.vendor,
                     subString: "KDE",
                     identity: "Konqueror"
               },
               {
                     string: navigator.userAgent,
                     subString: "Firefox",
                     identity: "Firefox"
               },
               {
                     string: navigator.vendor,
                     subString: "Camino",
                     identity: "Camino"
               },
               {               // for newer Netscapes (6+)
                     string: navigator.userAgent,
                     subString: "Netscape",
                     identity: "Netscape"
               },
               {
                     string: navigator.userAgent,
                     subString: "MSIE",
                     identity: "Explorer",
                     versionSearch: "MSIE"
               },
               {
                     string: navigator.userAgent,
                     subString: "Gecko",
                     identity: "Mozilla",
                     versionSearch: "rv"
               },
               {               // for older Netscapes (4-)
                     string: navigator.userAgent,
                     subString: "Mozilla",
                     identity: "Netscape",
                     versionSearch: "Mozilla"
               }
      ],
      dataOS : [
               {
                     string: navigator.platform,
                     subString: "Win",
                     identity: "Windows"
               },
               {
                     string: navigator.platform,
                     subString: "Mac",
                     identity: "Mac"
               },
               {
                     string: navigator.platform,
                     subString: "Linux",
                     identity: "Linux"
               }
      ]
 
};
BrowserDetect.init();
</pre>
 
Hope this is useful - it solved my problem   Smile | :)
GeneralRe: Freezing multiple columns Pinmemberoxid22:51 2 Jun '09  
GeneralRe: Freezing multiple columns Pinmembercyguard5:05 26 Aug '09  
QuestionDoes anyone have a demo posted somewhere that works on Firefox & Safari? Pinmembertactics23379:05 10 Apr '09  
AnswerRe: Does anyone have a demo posted somewhere that works on Firefox & Safari? Pinmembertactics233712:08 10 Apr '09  
GeneralRe: Does anyone have a demo posted somewhere that works on Firefox & Safari? Pinmemberstutid2:38 8 Dec '10  
GeneralNot working Pinmembersaulo benigno8:31 28 Jan '09  
QuestionEvents not firing with fixed columns [modified] Pinmembersrinian11:48 30 Sep '08  
AnswerRe: Events not firing with fixed columns PinmemberMember 79600836:35 27 May '11  
GeneralCheckboxes in Scrollable Table Pinmemberkusseal22:01 3 May '07  
GeneralRe: Checkboxes in Scrollable Table Pinmemberdubbele onzin9:59 29 Oct '07  
JokeFreezing Multiple Columns Pinmembersivad117814:14 25 Oct '06  
GeneralRe: Freezing Multiple Columns PinmemberHayw1r312:47 26 Nov '07  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120604.1 | Last Updated 25 Jun 2006
Article Copyright 2006 by Karin Huber
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid