Fixed Table Header

FixedTableHeader Demo Page with vanilla javascript

Please scroll page for see action and plugin description.
You will see fixed header foreach table when you scroll down the page.

SchoolDateTime1Time2Time 3Facility
tfoottestrow
School 101.01.200908:0010:0011:00Location of Event
School 202.01.200910:0012:0016:00Location of Event
School 303.01.20098:0010:0017:00Location of Event
School 404.01.200910:0010:4310:00Location of Event
School 505.01.200910:0010:0010:00Location of Event
School 606.01.200910:0021:0010:00Location of Event
School 707.01.200909:0010:0014:00Location of Event
School 808.01.200910:0014:0010:00Location of Event
School 909.01.20096:0010:0010:00Location of Event
School 1010.01.200910:0010:0010:00Location of Event
School 1111.01.200910:0010:0010:00Location of Event
School 1212.01.20097:0010:0010:00Location of Event
School 1313.01.200910:0015:0016:00Location of Event
School 1414.01.200910:0018:0019:00Location of Event
School 1515.01.200910:0010:0013:00Location of Event
School 1616.01.20096:0010:0012:00Location of Event
School 1717.01.200910:0016:0020:00Location of Event
School 1818.01.200910:0011:0017:00Location of Event
Courses--Autumn2009
Course Name CourseTutorSummaryCodeFee
Course Name 1Dr.JohnThe course will.......................H27$32
Course Name 2Dr.JohnThe course will.......................H28$18
Course Name 3Dr.JohnThe course will.......................H30$43
Course Name 4Dr.JohnThe course will.......................H27$32
Course Name 5Dr.JohnThe course will.......................H28$18
Course Name 6Dr.JohnThe course will.......................H30$18
Course Name 7Dr.JohnThe course will.......................H27$32
Course Name 8Dr.JohnThe course will.......................H28$18
Course Name 9Dr.JohnThe course will.......................H30$18
Course Name 10Dr.JohnThe course will.......................H27$32
Course Name 11Dr.JohnThe course will.......................H28$18
Course Name 12Dr.JohnThe course will.......................H30$18
TIMETABLE
STATIONASTATIONB
WorkDaysSaturdaySundayWorkDaysSaturdaySunday
05:5006:0006:2006:2006:3007:25
06:1006:1506:4006:5007:0007:45
06:3006:3007:0007:1007:1508:00
06:4506:4507:2007:3007:3008:20
¦07:00¦07:0007:3507:5007:4508:35
07:1007:1507:5008:1008:0008:50
¦07:10¦07:3008:0008:3008:2009:00
¦07:20¦07:4008:1008:4508:3509:15
07:3007:5008:2008:5508:5009:30
¦07:40¦08:0008:3009:0509:0509:45
07:5008:1008:5009:1509:1510:00
08:0008:2009:1009:3009:3010:15
¦08:15¦08:3009:3009:4509:4010:35
08:1508:4509:4510:0009:5010:50
08:3009:0010:0010:1510:0511:05
08:5009:1510:1010:3010:2011:15
09:1009:3010:2010:4510:3011:25
09:3009:4510:3511:0010:4511:45
09:4510:0010:4511:1511:0011:55
10:0010:1010:5511:3011:1512:05
10:1510:2511:1011:4511:3012:20
10:3010:3511:2512:0011:4512:35
10:4510:5011:4012:1512:0012:50
11:0011:0511:5512:3512:2013:05
11:1511:2012:1012:5512:3513:20
11:3011:3512:2013:1012:5013:30
11:4511:5512:3513:2513:0013:45
12:0512:1512:5013:4013:2014:00
12:2012:3513:0014:0013:4014:10
12:3512:5013:1014:2013:5514:20
12:5013:0513:2514:3514:1014:35
13:0513:2513:4014:5514:2514:50
13:2013:4513:5515:1014:4015:05
13:3514:0514:1015:3015:0015:20
13:5014:2014:2515:5015:2015:35
14:1014:3514:3516:0515:4015:45
14:3014:5514:5016:2516:1016:00
14:5015:1015:0516:4016:3016:15
15:1015:2515:1517:0016:5016:25
15:3015:4015:25¦17:10¦17:0516:35
15:5015:5515:4017:1517:2016:50
16:0516:1515:5517:3017:3517:05
16:2016:3516:1017:4517:5017:20
16:3516:5516:2517:5518:1017:30
16:5517:1016:4018:1518:3017:50
17:1517:3016:50¦18:30¦18:4518:00
17:3017:4517:0518:4519:0018:20
17:4518:0017:2519:0519:1518:30
18:0018:1517:4019:2019:3018:45
¦18:10¦18:3017:55¦19:35¦19:4519:00
18:2018:5018:1519:4520:0019:15
18:4019:1018:3520:0520:2019:40
19:0019:3018:5020:2520:4019:55
19:1519:5019:0520:4021:0020:10
19:3020:1019:2020:5521:2020:25
19:4520:3519:3521:1521:4520:40
20:0521:0019:5521:3022:1021:00
20:3021:3020:1521:5022:4021:25
21:0022:0020:4522:1023:1021:55
21:3021:1022:4022:20
22:0023:10

Just copy the code below and paste it into a file, then call it at the bottom of your page.

Important : for code to work, structure of tables must be as follow :


<div id="content">
<table>
        <thead>
            <tr>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td></td>
            </tr>
        </tbody>
</table>
</div>

and should be within a unique div tag with an id (one div for all tables).
The script needs two params : the id of the wrapper and marging (number) between top of the page and bottom of an eventualy fixed block (as menu for example). This marging can be equal to zero.


    var idContainer = 'DemoContent';
   
/*
# ------------------ BEGIN LICENSE BLOCK ------------------
#
# This file is part of SIGesTH
#
# Copyright (c) 2009 - 2015 Cyril MAGUIRE, 
# Licensed under the CeCILL v2.1 license.
# See http://www.cecill.info/licences.fr.html
#
# ------------------- END LICENSE BLOCK -------------------
*/

// marge est la distance entre le haut de la fenêtre et le haut du tableau 
// à partir de laquelle l'entête du tableau est en position fixe
// Utile s'il y a un bloc fixe en haut de page, afin d'éviter que l'entête du tableau ne s'affiche en dessous
if (marge == undefined || marge == null || isNaN(marge) == true) var marge = 0;
if (idContainer == undefined || idContainer == null) {  
    var idContainer;
    alert('Message de fixedTableHeader.js : Vous devez définir un container !');
}
if (document.getElementById(idContainer) == undefined) {  
    alert('Message de fixedTableHeader.js : Le container '+idContainer+' n\'existe pas !');
}
;(function(idContainer,marge,window,undefined){
        'use_strict';
        
        if (idContainer == undefined || idContainer == null) return;

        // On convertit en integer en utilisant |0 à la place de parseInt moins fiable
        // voir http://www.js-attitude.fr/2012/12/26/convertir-un-nombre-en-texte-en-javascript/
        marge = (marge|0);

        var isIE = isBrowserIE();
        function isBrowserIE() {
            //var isMSIE = /*@cc_on!@*/0;  
            var nav = navigator.userAgent.toLowerCase();
            return (nav.indexOf('msie') != -1) ? (nav.split('msie')[1]|0) : false;
        }

        function Remove(idOfParent,idToRemove) {
            if (isIE) {
                document.getElementById(idToRemove).removeNode(true);
            } else {
                var Node1 = document.getElementById(idOfParent); 
                var len = Node1.childNodes.length;

                for(var i = 0; i < len; i++){           
                    if (Node1.childNodes[i] != undefined && Node1.childNodes[i].id != undefined && Node1.childNodes[i].id == idToRemove){
                        Node1.removeChild(Node1.childNodes[i]);
                    }
                }
            }   
        }

        function addElement(DomParent,elemToAdd,htmlToInsert,id) {

            var newElement = document.createElement('div');
            document.body.appendChild(newElement);
            newElement.innerHTML = htmlToInsert;
            if (id != '') {
                newElement.setAttribute('id',id+'_wrapper');
            }
        }

        function getScrollPosition() {
            return Array(
                (document.documentElement && document.documentElement.scrollLeft) || window.pageXOffset || self.pageXOffset || document.body.scrollLeft,
                (document.documentElement && document.documentElement.scrollTop) || window.pageYOffset || self.pageYOffset || document.body.scrollTop
                );
        }

        function arrayCompare(a1, a2) {
            if (a1.length != a2.length) return false;
            var length = a2.length;
            for (var i = 0; i < length; i++) {
                if (a1[i] !== a2[i]) return false;
            }
            return true;
        }
        function inArray(needle, haystack) {
            var length = haystack.length;
            for(var i = 0; i < length; i++) {
                if(typeof haystack[i] == 'object') {
                    if(arrayCompare(haystack[i], needle)) return true;
                } else {
                    if(haystack[i] == needle) return true;
                }
            }
            return false;
        }

        /*
        Récupère la position réelle d'un objet dans la page (en tenant compte de tous ses parents)
        IN  : Obj => Javascript Object ; Prop => Offset voulu (offsetTop,offsetLeft,offsetBottom,offsetRight)
        OUT : Numérique => position réelle d'un objet sur la page.
        */
        function GetDomOffset( Obj, Prop ) {
            var iVal = 0;
            while (Obj && (Obj.tagName != 'body' || Obj.tagName != 'BODY')) {
                eval('iVal += Obj.' + Prop + ';');
                Obj = Obj.offsetParent;
            }
            return iVal;
        }

        var fixedHeaderTable = {
            tbl:[],
            init: function() {
                this.addEventListener(window,"scroll", this);
            },
            addEventListener: function(el, eventName, handler) {
                if (el.addEventListener) {
                    el.addEventListener(eventName, handler, false);
                } else {
                    el.attachEvent('on' + eventName, fixedHeaderTable.handleEvent);
                }
            },
            handleEvent: function(e) {
                var evt = e ? e:event;
                if (isIE) {
                    var obj = null;
                    obj = fixedHeaderTable;
                } else {
                    var obj = Object.create(null);
                    obj = this;
                }
                var pos = getScrollPosition();
                var tabPos = []; 
                var c = obj.tbl.length;
                for (var i = 0; i < c; i++) {
                    if (obj.tbl[i] != undefined) {
                        tabPos[i] = (obj.tbl[i].posTop - (2*marge)) ;
                        pos[1] += obj.tbl[i].containerPosTop;
                        if (obj.tbl[i].posTop > 0 && obj.tbl[i].posBottom > 0) {       
                            if (pos[1] < tabPos[i]) {
                                obj.action(i,false);
                            }
                            if (pos[1] > (tabPos[i]) ) {
                                obj.action(i,true);
                            }
                            if (pos[1] >= obj.tbl[i].posBottom ) {
                                obj.action(i,false);
                            }
                        }
                    }
                };
            },
            action: function(i,display) {
                var clone = document.getElementById('fixedtableheader'+i);
                if (clone) {
                    if (display === true) {
                        clone.style.display = 'block';
                    }
                    if (display === false) {
                        clone.style.display = 'none';
                    }
                }
            },
        };

        var posBottom = 0;
        var posTop = 0;
        var mainDiv = document.getElementById(idContainer);
        var disp = mainDiv.style.display;
        if (disp == '') { disp = 'block'; }
        var containerPosTop = 0;
        // afin de déterminer la position des tableaux, on affiche le bloc qui les contient, s'il est masqué
        if(disp != 'block') {
            mainDiv.style.display = 'block';
            containerPosTop = GetDomOffset( mainDiv, 'offsetTop' );
        }
        var tables = mainDiv.getElementsByTagName('table');
        // widths est l'ensemble des largeurs de colonnes pour chaque tableau
        var widths = [];
        if (tables.length > 0){
            var c = tables.length;
            for (var i = 0; i < c; i++) {
                var lastcell = tables[i].getElementsByTagName('tr');
                for (var j = 0,clast = lastcell.length; j <= clast; j++) {
                    if(j == 0) { posTop = GetDomOffset(lastcell[0],'offsetTop');}
                    if(j == clast) { posBottom = GetDomOffset(lastcell[j-1],'offsetTop');}
                }
                fixedHeaderTable.tbl[i] = new Object();
                fixedHeaderTable.tbl[i].table = tables[i]; 
                fixedHeaderTable.tbl[i].containerPosTop = containerPosTop;
                fixedHeaderTable.tbl[i].posTop = posTop;
                fixedHeaderTable.tbl[i].posBottom = posBottom;
                fixedHeaderTable.tbl[i].posLeft = GetDomOffset( tables[i], 'offsetLeft' );
    
                var size = [];
                size[i] = tables[i].getElementsByTagName('thead');
                var tableBody = [];
                tableBody[i] = tables[i].getElementsByTagName('tbody');
                if (size[i][0]) {
                    var nbOfRowsInHead = size[i][0].getElementsByTagName('tr').length;
                }
                // TBODY
                var tBod = tables[i].getElementsByTagName('tbody');
                if (tBod[0]) {
                    var nbOfRows = tBod[0].getElementsByTagName('tr');
                }
                var nbOfCols = 0;
                var cellsOfThisRow = [];
                if (nbOfRows) {
                    var allRowsOfBody = nbOfRows.length;
                    for (var thisRow = 0; thisRow < allRowsOfBody; thisRow++) {
                        cellsOfThisRow[thisRow] = nbOfRows[thisRow].getElementsByTagName('td');
                        var nbOfTdInThisRow = cellsOfThisRow[thisRow].length;
                        nbOfCols = (nbOfTdInThisRow >= nbOfCols ? nbOfTdInThisRow : nbOfCols);
                    }
                }

                var colWidth = [];
                var indexOfCol = 0;
                if (allRowsOfBody) {
                    for (var row = 0; row < allRowsOfBody; row++) {           
                        var nbOfCells= cellsOfThisRow[row].length;                 
                        for (var cel = 0; cel < nbOfCells; cel++) {
                            var widthOfThisCell = cellsOfThisRow[row][cel].offsetWidth;
                            colWidth[indexOfCol] = (widthOfThisCell > colWidth[indexOfCol] ? colWidth[indexOfCol] : widthOfThisCell);
                            indexOfCol++;
                        }
                    }
                }
                // THEAD
                indexOfCol = 0;
                if (size[i][0]) {
                    // largeurs est l'ensemble des largeurs de colonnes pour un tableau donné
                    var largeurs = [];
                    widths[i] = [];
                    var dec = 0
                    var decalage = [];
                    
                        
                    // Pour chaque ligne d'entête
                    for (var k = 0; k < nbOfRowsInHead; k++) {
                        if (size[i][k] != undefined) {
                            if(disp == 'block') {
                                fixedHeaderTable.tbl[i].posBottom -= ((size[i][k].offsetHeight/2)+marge);
                            } else {
                                fixedHeaderTable.tbl[i].containerPosTop -= ((size[i][k].offsetHeight*2)+marge);
                            }
                            var cells = size[i][k].getElementsByTagName('th');
                            var ccells = cells.length;
                            for (var l = 0; l < ccells; l++) {
                                largeurs[indexOfCol] = colWidth[indexOfCol];
                                if (indexOfCol 3) {
                                        var colsp = cells[larg].getAttribute('colspan');
                                        if (colsp == null) {
                                            widths[i].push(largeurs[larg]);
                                        }
                                    } else {
                                        widths[i].push(largeurs[larg]);
                                    }
                                    var rowsp = cells[larg].getAttribute('rowspan');
                                    if (rowsp == null) {
                                        decalage[dec] = largeurs[larg];
                                        dec++;
                                    }
                                }
                            }
                            var clone = size[i][k].cloneNode(true);
                            var cloneBody = tableBody[i][k].cloneNode(true);
                            var mainTab = document.createElement('div');
                            mainDiv.setAttribute('style', 'position:relative;');
                            cloneBody.setAttribute('style', 'visibility:hidden;border:none;');
                            var cellsBodyFake = cloneBody.getElementsByTagName('td');
                            var nbTds = cellsBodyFake.length;
                            for (var m = 0; m < nbTds; m++) {
                                cellsBodyFake[m].setAttribute('style','border:none;');
                            };
                            mainDiv.appendChild(mainTab);
                            mainTab.innerHTML = '
'; mainTab.setAttribute('style', 'position:relative;border:none;'); var tabFake = document.getElementById('fixedtableheader'+i); tabFake.setAttribute('style', 'display:block;position:fixed;top:'+marge+'px;max-width:'+mainTab.offsetWidth+'px;background:transparent;border:none;'); tabFake.style.display = 'none'; tabFake.appendChild(clone); tabFake.appendChild(cloneBody); var rowsFake = document.getElementById('fixedtableheader'+i).getElementsByTagName('tr'); var cellsFake = rowsFake[0].getElementsByTagName('th'); var indexOfWidth = 0; for (var m = 0; m < nbOfCols; m++) { if (cellsFake[m] != undefined) { var rowsp = cellsFake[m].getAttribute('rowspan'); if (rowsp == null && decalage[m] != undefined) { if (cellsFake[m].getAttribute('style') != '') { cellsFake[m].setAttribute('style','min-width:'+decalage[m]+'px;'); } } var colsp = cellsFake[m].getAttribute('colspan'); if (colsp != null && rowsp == null) { if (cellsFake[m].getAttribute('style') != '') { var w = 0; var colspanWidth = 0; while (w != colsp) { colspanWidth += widths[i][m+w]; w++; } cellsFake[m].setAttribute('style','min-width:'+colspanWidth+'px;'); indexOfWidth = m+w-1; } } else { if (cellsFake[m].innerHTML == '') { cellsFake[m].innerHTML = ' '; cellsFake[m].setAttribute('style','min-width:'+widths[i][indexOfWidth]+'px;'); } if (cellsFake[m].getAttribute('style') != '') { cellsFake[m].setAttribute('style','width:'+widths[i][indexOfWidth]+'px;'); } } indexOfWidth++; } }; delete clone; delete cloneBody; } } } else { fixedHeaderTable.tbl.splice(i,1); } }; fixedHeaderTable.init(); } mainDiv.style.display = disp; })(idContainer,marge,window);

Enjoy !

Known issue

Width of cells of header can be less large than columns. I don't know how to fix it. Help will be great. Thanks (fixed).

Last update 27/02/2015