Feb 2, 2016

Table with fixed header and fixed columns

Usually we need a fixed header when showing big table.
In order to keep track of which column it was you need the header <thead> fixed.

Sometimes you even need the columns fixed in case you have a big horizontal scroll on table, i.e. when you have more than 10-15 columns.

Usually we duplicate the header <thead> and make it position fixed, but that causes irregularities in the header and table columns width.

Here is a solution to that:

jsFiddle Demo

HTML


<div class="tblScroll" id="fixedTable1">
    <table class="table table-bordered">
      <thead>
        <tr>
          <th>Firstname</th>
          <th>Lastname</th>
          <th>Email</th>
          <th>Firstname 2</th>
          <th>Lastname 2</th>
          <th>Email 2</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>John</td>
          <td>Doe</td>
          <td>john@example.com</td>          
          <td>John 2</td>
          <td>Doe 2</td>
          <td>john@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>John</td>
          <td>Doe</td>
          <td>john@example.com</td>
          <td>John 2</td>
          <td>Doe 2</td>
          <td>john@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>John</td>
          <td>Doe</td>
          <td>john@example.com</td>
          <td>John 2</td>
          <td>Doe 2</td>
          <td>john@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>John</td>
          <td>Doe</td>
          <td>john@example.com</td>
          <td>John 2</td>
          <td>Doe 2</td>
          <td>john@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>John</td>
          <td>Doe</td>
          <td>john@example.com</td>
          <td>John 2</td>
          <td>Doe 2</td>
          <td>john@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
        <tr>
          <td>Mary</td>
          <td>Moe</td>
          <td>mary@example.com</td>
          <td>Mary 2</td>
          <td>Moe 2</td>
          <td>mary@example.com 2</td>
        </tr>
        <tr>
          <td>July</td>
          <td>Dooley</td>
          <td>july@example.com</td>
          <td>July 2</td>
          <td>Dooley 2</td>
          <td>july@example.com 2</td>
        </tr>
      </tbody>
    </table>  
</div> 
CSS


body{padding-bottom:3000px; transform: translateZ(0);
 backface-visibility: hidden;
 perspective: 1000;}

.clearfix:before,
.clearfix:after {
  content: '.';
  display: block;
  overflow: hidden;
  visibility: hidden;
  font-size: 0;
  line-height: 0;
  width: 0;
  height: 0;
}
.clearfix:after {
  clear: both;
}
.clearfix {
  zoom: 1;
}

.tblScrollWrap {
  overflow:auto;
  max-width:100%;margin-bottom:20px;margin:0;
} 
.tblScroll {overflow:auto;} 
.tblScroll .table {
  max-width:initial;
  min-width:100%;
  width:auto;
  margin-bottom:0;
  border:none;
  
} 
.tblScroll > .table {
  border-collapse: separate;
}
.table td {
   min-width:100px; 
   padding:10px;
}
.table th {
  background:#ccc;
     padding:10px;
}

.tblScroll .table-bordered > tbody > tr > td, .tblScroll .table-bordered > tbody > tr > th, .tblScroll .table-bordered > tfoot > tr > td, .tblScroll .table-bordered > tfoot > tr > th, .tblScroll .table-bordered > thead > tr > td, .tblScroll .table-bordered > thead > tr > th {
  border:none;
  border-right:1px solid #ddd;
  border-bottom:1px solid #ddd;
}
.table td {
  background:#fff;
}
.fxHead, .fxCol {
  position:relative;
  -webkit-backface-visibility: hidden;
-webkit-perspective: 1000;
}
.fxHead {
  z-index:20;
}
.opacityTransition {
   transition:opacity 200ms ease; 
}
.fxCol {
  z-index:19;
}
.fxHead.fxCol {
  z-index:21;
}
.fxHead, .fxCol {
}

.tblHorScroll {
  width:100%;
  overflow-x:auto;
  overflow-y:hidden;
  transform:translate(0,-100%);
  -ms-transform:translate(0,-100%);
}
.tblHorWidth{
  height:1px;
}
JS

if("document" in self){if(!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p<o;p++){if(p in this&&this[p]===q){return p}}return -1},n=function(o,p){this.name=o;this.code=DOMException[o];this.message=p},g=function(p,o){if(o===""){throw new n("SYNTAX_ERR","An invalid or illegal string was specified")}if(/\s/.test(o)){throw new n("INVALID_CHARACTER_ERR","String contains an invalid character")}return c.call(p,o)},d=function(s){var r=k.call(s.getAttribute("class")||""),q=r?r.split(/\s+/):[],p=0,o=q.length;for(;p<o;p++){this.push(q[p])}this._updateClassName=function(){s.setAttribute("class",this.toString())}},e=d[f]=[],i=function(){return new d(this)};n[f]=Error[f];e.item=function(o){return this[o]||null};e.contains=function(o){o+="";return g(this,o)!==-1};e.add=function(){var s=arguments,r=0,p=s.length,q,o=false;do{q=s[r]+"";if(g(this,q)===-1){this.push(q);o=true}}while(++r<p);if(o){this._updateClassName()}};e.remove=function(){var t=arguments,s=0,p=t.length,r,o=false,q;do{r=t[s]+"";q=g(this,r);while(q!==-1){this.splice(q,1);o=true;q=g(this,r)}}while(++s<p);if(o){this._updateClassName()}};e.toggle=function(p,q){p+="";var o=this.contains(p),r=o?q!==true&&"remove":q!==false&&"add";if(r){this[r](p)}if(q===true||q===false){return q}else{return !o}};e.toString=function(){return this.join(" ")};if(b.defineProperty){var l={get:i,enumerable:true,configurable:true};try{b.defineProperty(m,a,l)}catch(h){if(h.number===-2146823252){l.enumerable=false;b.defineProperty(m,a,l)}}}else{if(b[f].__defineGetter__){m.__defineGetter__(a,i)}}}(self))}else{(function(){var b=document.createElement("_");b.classList.add("c1","c2");if(!b.classList.contains("c2")){var c=function(e){var d=DOMTokenList.prototype[e];DOMTokenList.prototype[e]=function(h){var g,f=arguments.length;for(g=0;g<f;g++){h=arguments[g];d.call(this,h)}}};c("add");c("remove")}b.classList.toggle("c3",false);if(b.classList.contains("c3")){var a=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(d,e){if(1 in arguments&&!this.contains(d)===!e){return e}else{return a.call(this,d)}}}b=null}())}}; 



var opts = {
  container: '#fixedTable1',
  header: 'fixed',
  topOffset: 50,
  fixedcolumns: 2,
  sizepolling: 200
}

function fixedTable(opts) {
  var thisTable = {};
  thisTable.preScroll = null;
  thisTable.scrollStopEvents = [];
  thisTable.scrollHorStopEvents = [];
  var defaults = {
    header: 'fixed',
    fixedcolumns:0,
    topOffset: 0
  };

  var init = function() {

    for(x in defaults) {
      if(! opts.hasOwnProperty(x)) {
        opts[x] = defaults[x];
      }
    }

    detectTable();
    if(opts.header.toLowerCase() === 'fixed') {
      initiateFixedHeader();
    }
    if(opts.fixedcolumns > 0) {
      initiateFixedColumns();
    }

    handleTableEvents();
  };

  var handleTableEvents = function() {
    thisTable.container.addEventListener("scroll",syncScroll);
    thisTable.container.addEventListener("scroll",horScrollListener);
    if(typeof thisTable.scrollCont != 'undefined') {
      thisTable.scrollCont.addEventListener("scroll",syncScroll);
    }
  };

  var horScrolldelay = 50;
  var horScrolltimeout = null;
  var horScrollListener = function(e) {
    clearTimeout(horScrolltimeout);
    horScrolltimeout = setTimeout(function(){
      horizontalScrollStopDispatcher();
    },horScrolldelay);
  };
    var syncScroll = function(e) {
      if(thisTable.preScroll != null && thisTable.preScroll != e.target){return;}
      if(thisTable.preScroll == null){
        thisTable.onHorTableScrollStopped(function() {
          thisTable.preScroll = null;
        })
      }
      thisTable.preScroll = e.target;
      var scrollLeft = (typeof e.explicitOriginalTarget != 'undefined')? parseInt(e.explicitOriginalTarget.scrollLeft) : parseInt(e.srcElement.scrollLeft);
      if(e.target == thisTable.container) {
        thisTable.scrollCont.scrollLeft = e.target.scrollLeft;
        positionFixedCols(e.target.scrollLeft);
      }
      if(e.target == thisTable.scrollCont) {
        positionFixedCols(e.target.scrollLeft);
        thisTable.container.scrollLeft = e.target.scrollLeft
      }
    };
    var positionFixedCols = function(scrollLeft) {
      for(var i = 1; i <= opts.fixedcolumns; i++) {
        var colHead = thisTable.container.querySelectorAll('table > thead > tr th:nth-child('+i+')');
        var colBody = thisTable.container.querySelectorAll('table > tbody > tr td:nth-child('+i+')');
        [].forEach.call(colHead,function(element, index, array) {
          element.style.transform = setRule(element.style.transform,'translateX',(scrollLeft+'px'));
          if(! element.classList.contains('fxCol')) {
            element.classList.add('fxCol');
          }
          if(prefix.lowercase == 'ms') {
            element.style.msTransform = setRule(element.style.msTransform,'translateX',(scrollLeft+'px'));
          }
        });

        [].forEach.call(colBody,function(element, index, array) {
         element.style.transform = setRule(element.style.transform,'translateX',(scrollLeft+'px'));
          if(! element.classList.contains('fxCol')) {
            element.classList.add('fxCol');
          }
          if(prefix.lowercase == 'ms') {
            element.style.msTransform = setRule(element.style.msTransform,'translateX',(scrollLeft+'px'));
          }
        });

      }
    };
    var initiateFixedColumns = function() {
      thisTable.scrollCont = document.createElement('div');
      thisTable.scrollCont.className = "tblHorScroll";
      thisTable.scrollWidthCont = document.createElement('div');
      thisTable.scrollWidthCont.className = "tblHorWidth";

      thisTable.scrollWidthCont.style.width = thisTable.table.getBoundingClientRect().width + 'px';

      thisTable.scrollCont.appendChild(thisTable.scrollWidthCont);
      var parCont = thisTable.parentNode;
       thisTable.container.parentNode.insertBefore(thisTable.scrollCont, thisTable.container.nextSibling);
    };

  var isIE = (function() {
      var myNav = navigator.userAgent.toLowerCase();
      if(myNav.indexOf('msie') != -1) {
        var ieVer =  parseInt(myNav.split('msie'))[1];
        if(myNav.indexOf('msie 10') > -1 || myNav.indexOf('msie 9') > -1)  {return true}
      }
      return false;
    })();

    var prefix = (function () {
      var styles = window.getComputedStyle(document.documentElement, ''),
        pre = (Array.prototype.slice
          .call(styles)
          .join('') 
          .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
        )[1],
        dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];
      return {
        dom: dom,
        lowercase: pre,
        css: '-' + pre + '-',
        js: pre[0].toUpperCase() + pre.substr(1)
      };
    })();
 
  
  var detectTable = function() {
    if(typeof opts.container == 'undefined' || opts.container.trim() == '') {
      throw "Container missing"; 
    }else {
      thisTable.container = document.querySelector(opts.container);
      if(typeof thisTable.container == "undefined" || thisTable.container === null) {
        throw "Container missing"; 
      }
      thisTable.table = thisTable.container.querySelector('table');
      if(typeof thisTable.table == "undefined" || thisTable.table === null) {
        throw opts.container + " does not contain a table element"; 
      }
      thisTable.thead = thisTable.table.querySelector('thead');
      thisTable.tbody = thisTable.table.querySelector('tbody');
    }
  };

  var setRule = function(rule,prop,val) {
    rule = rule || '';
    var str = rule+'';
    var regex = new RegExp(prop +"\\(.*?\\)","gi");
    if (str.match(regex) != null) {
      str = str.replace(regex,prop+'('+val+')')
    }else {
      str += ' ' + (prop+'('+val+')');
    }
    return str;
  } 

  
  
  thisTable.onWindowScrollStopped = function(fnc) {
    thisTable.scrollStopEvents.push(fnc);
  };

  thisTable.onHorTableScrollStopped = function(fnc) {
    thisTable.scrollHorStopEvents.push(fnc);
  };

  var verticalScrollStopDispatcher = function() {
    for(var i = 0; i <  thisTable.scrollStopEvents.length; i++) {
      thisTable.scrollStopEvents[i].call();
    }
  }
  var horizontalScrollStopDispatcher = function() {
    for(var i = 0; i <  thisTable.scrollHorStopEvents.length; i++) {
      thisTable.scrollHorStopEvents[i].call();
    }
  }

  var scrolldelay = 500;
  var scrolltimeout = null;

  var winScrollListener = function() {
    clearTimeout(scrolltimeout);
    scrolltimeout = setTimeout(function(){
        verticalScrollStopDispatcher();
    },scrolldelay);
  }

  var moveHeader = function(e) {
    var tableY = (typeof thisTable.table.getBoundingClientRect().y != 'undefined')? thisTable.table.getBoundingClientRect().y : thisTable.table.getBoundingClientRect().top;
    var tableHeight = thisTable.table.getBoundingClientRect().height;
    if(tableY < 0) {
      applyHeaderStyles(tableY * -1);
    }
    if(tableY > 0) {
      applyHeaderStyles(0);
    }
    thisTable.prevY = tableY;
  };

  var applyHeaderStyles = function(scrollTop) {
    var headers = thisTable.table.querySelectorAll('thead th');
    [].forEach.call(headers,function(element, index, array) {
      if(isIE) {
        element.style.opacity = 0;
        element.classList.add('opacityTransition');  
      }
      element.style.transform = setRule(element.style.transform,'translateY',( scrollTop+'px'));
      if(! element.classList.contains('fxHead')) {
        element.classList.add('fxHead'); 
      }
      if(prefix.lowercase == 'ms') {
        element.style.msTransform = setRule(element.style.msTransform,'translateY',(scrollTop+'px'));
      }
    });
  };
  var initiateFixedHeader = function() {
    window.addEventListener("scroll",moveHeader);
    window.addEventListener("scroll",winScrollListener);
    if(isIE) {
       thisTable.onWindowScrollStopped(function(){
        var headers = thisTable.table.querySelectorAll('thead th.opacityTransition');
        [].forEach.call(headers,function(element, index, array) {
          
          setTimeout(function(){
            element.style.opacity = 1;
            setTimeout(function(){
              element.classList.remove('opacityTransition');    
            },200);
          },500)
        });
      }); 
    }
  };

  init();

  return {
    destroy : 'empty'
  }

}

var b = fixedTable(opts);






No comments:

Post a Comment

Test post after a long time

i want to see the post