^ Click Here

Thursday, March 8, 2012

Sticky columns/headers (Freeze pane) in tables through jQuery

Many times you have seen that the columns or headers freezes when you scroll horizontally or vertically respectively in a table. It looks rather cool but more than that its very useful hence nowhere you loses the track on any cell as which column and which header it belongs to irrespective of where the cell is in large table.
Let's see what can be done

the css


#resultTable{
    width:1000px;
    height:500px;
    clear:both;
    overflow:auto;
    position:relative;
}
.sticky-col-table{
    background-color: #eee;
    margin-left:0px !important;
}
.sticky-row-table {
    background-color: #eee;
    margin-top:0px !important;
}

.scrollTable{

    border:1px solid red;
    margin-top:5px;
    text-align:center;
}
.scrollTable td,.scrollTable th{
    width:60px;
    padding:5px;
    border:1px solid green;
}
now in the body let's put this structure


<div id="condDiv">
Columns : <input type="text" id="inputColumn" value="50"/> <br />
Rows : <input type="text" id="inputRow" value="50"/><br />
<input type="button" value="Create Table" onclick="return val_table();"/>
</div><br />
<div id="resultTable"  onscroll="movestickyColumn(this)">
<div id="sticky-column" style="position:absolute;display:none;"> </div>
<div id="sticky-row" style="position:absolute;display:none;"> </div>
</div>
let me describe it a bit in condDiv div I have two input elements where a person can provide the number of rows and columns for creating a dynamic table by clicking on the create Table button

the resultTable div is the div which will hold the dynamic table created and on scrolling of the div the function movestickyColumn(this) is called with passing the div object to it.

also note that it holds two empty and hidden div sticky-column and sticky-row (idea is to hold the frozen part of table in these divs).

Now the script part one by one, first for creating dynamic table we will have functions as follow

 validations -


function val_table(){
    var row= $('#condDiv #inputRow').val();
    var column = $('#condDiv #inputColumn').val();
    if(typeof row == "undefined" || row == null || row == "" || row <= 0 || isNaN(row) || typeof column == "undefined" || column == null || column == "" || column <= 0 || isNaN(column)){
        alert("Please provide valid positive value for rows and columns");
        return false;
        }
    else if(row>500 || column>500){
        alert("The column or row can not be more than 500");
        return false;
    }
    else{
        if($("#resultTable #myTable").length > 0){
            $("#resultTable #myTable").remove();
            }
        $("#resultTable").append(create_table(column,row));
        stickyColumnNew(2);
        stickyRow();
        }
}

first of all I am validating the number of row and columns provided in the input field, if it is I am calling three functions

for creating table


function create_table(column,row){
    var tableArray = [];
  
    var tableHead = [];
    var tableBody = [];
    tableHead.push("<thead><tr>");
    tableBody.push("<tbody>");
    var count = 0;
    for(ro=0; ro<row; ro++){  
        tableBody.push("<tr>");
            for(col=0; col<column; col++){
                if(ro==0){
                        tableHead.push("<th>Head "+(col+1)+"</th>")
                    }
                if((count%column) == 0){
                tableBody.push("<td> cell-"+(count+1)+" row-"+(ro+1)+"</td>");
                }
                else{
                    tableBody.push("<td> cell-"+(count+1)+"</td>");
                    }
                    count++;
                }
        }
    tableHead.push("</tr></thead>");
    tableBody.push("</tbody>");
    tableArray.push("<table id='myTable' class='scrollTable'>"+tableHead.join("")+tableBody.join("")+"</table>");
    return tableArray.join("");
}

then for getting the frozen columns to be formed in the sticky-column div, function stickyColumnNew(2) is called. note that I am passing 2 as an argument to freeze first 2 columns, you can pass more or less.


function stickyColumnNew(col) {
    $("#sticky-column").html('');
    // var trlength = $('table.table_sorter tbody tr').length;      
    var documentFragment =[];
    documentFragment.push('<table class="sticky-col-table scrollTable">');
    var docHead = [];
    docHead.push("<thead><tr>");
    var docBody = [];
    docBody.push("<tbody>")
    var count = 0;
    $("#myTable thead tr th").each(function(){
        if(count < col){
        docHead.push("<th style='height:"+$(this).outerHeight()+";'>"+$(this).html()+"</th>");  //use outerHeight() to get the height with padding, border and margin
        }
        count++;
    });
    docHead.push("</tr></thead>");
    $("#myTable tbody tr").each(function(){
    docBody.push("<tr>")
        if(col >= 1){
            docBody.push("<td style='height:"+$(this).find('td:first').outerHeight()+";'>"+$(this).find('td:first').html()+"</td>");
        }
        if(col >= 2){
        for(val=2; val<=col; val++){
            docBody.push("<td>"+$(this).find('td:nth-child('+val+')').html()+"</td>");
            }
        }
        docBody.push("</tr>")
    });
    docBody.push("</tbody>");
    documentFragment.push(docHead.join("")+docBody.join("")+'</table>');
    $("#sticky-column").append(documentFragment.join(''));  
    }

Basically I am creating a dummy table with only the 2 columns I have to freeze, It's cell should have the same height and content as the original table and I am simply putting that table in sticky-column div

In the same fashion I will create the table with dummy header with function stickyRow


function stickyRow() {
    $("#sticky-row").html('');
    // var trlength = $('table.table_sorter tbody tr').length;      
    var documentFragment =[];
    documentFragment.push('<table class="sticky-row-table scrollTable">');
    var docHead = [];
    docHead.push("<thead><tr>");
    $("#myTable thead tr th").each(function(){
      
        docHead.push("<th style='height:"+$(this).outerHeight()+";'><div style='width:"+$(this).width()+";'>"+$(this).html()+"</div></th>");  //use outerHeight() to get the height with padding, border and margin
      
    });
    docHead.push("</tr></thead>");
    documentFragment.push(docHead.join("")+'</table>');
    $("#sticky-row").append(documentFragment.join(''));
    }

Now the only part left is the movestickyColumn on the div


function movestickyColumn(tableDiv) {
    posX = tableDiv.scrollLeft;
    posY = tableDiv.scrollTop;
    if(posX > 50) {
        $('table.sticky-col-table,#sticky-column').css('display','block');
        $('table.sticky-col-table,#sticky-column').css('left',posX+'px');
    }
    else{
        $('table.sticky-col-table,#sticky-column').css('display','none');
        }
      
    if(posY > 50){
    console.log(posY);
        $('table.sticky-row-table,#sticky-row').css('display','block');
        $('table.sticky-row-table,#sticky-row').css('top',posY+'px');
    }
    else{
        $('table.sticky-row-table,#sticky-row').css('display','none');
    }  
}

So actually I am checking that as soon as the vertical or horizontal scroll crosses the 50px margin I am displaying the sticky-row and sticky-column divs respectively and accordingly.

feel free to play, customize or enhance upon it

hope it helps.

Try it here : -

[As far as I have checked there is some problem as in firefox header row width is not aligned and in chrome the columns height are not aligned. I guess some conflict with blogger css and css I am using. Trying to find the solution. However independently I have checked in both browser as working fine.]  



Columns :
Rows :

1 comment: