Click here to Skip to main content
15,892,005 members
Articles / Web Development / ASP.NET

"One size fits all" solution for freezing a Grid's header rows, why not?

Rate me:
Please Sign up or sign in to vote.
4.91/5 (11 votes)
23 Jan 2013CPOL8 min read 44.3K   1.2K   20  
Enhance usability and look-and-feel of table/grid with freezing header rows. Let's discover it.
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;

public partial class FrozenGridHeader_SafariFlock : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        DataProvider objDataProvider = new DataProvider();
        gvCustomers.DataSource = objDataProvider.GetCustomers();
        gvCustomers.DataBind();

        System.Web.HttpBrowserCapabilities browser = Request.Browser;

        //if (browser.Browser == "AppleMAC-Safari")
        if (Request.UserAgent.ToLower().IndexOf("chrome") > 0)
            FreezeHeaderRowUponScroll();
        else
            if (Request.UserAgent.ToLower().IndexOf("safari") > 0)
            {
                pageHeader.InnerHtml = "Freezing Grid Header Row - Safari Compatability";
                FreezeHeaderRowUponScroll_OnSafariFlock();
            }
            else
                if (Request.UserAgent.ToLower().IndexOf("flock") > 0)
                {
                    pageHeader.InnerHtml = "Freezing Grid Header Row - Flock Compatability";
                    FreezeHeaderRowUponScroll_OnSafariFlock();
                }
                else //Firefox, IE >=8, Opera...
                    if (!(browser.Browser == "IE" && int.Parse(browser.Version.Substring(0, 1)) < 8))
                        FreezeHeaderRowUponScroll();
    }

    private void FreezeHeaderRowUponScroll_OnSafariFlock()
    {
        //NOTE: This JQuery script must be inserted at code-bebind to make sure dynamic loading is running fine, otherwise it doesn't take into effect for the subsequent postbacks.
        string jQuery = @"
             <script type=""text/javascript"">
                var gridview_header_top_position = 0;
                $(document).ready(function() {
                    var divHeaderRow = null;

                    $(window).scroll(function() 
                    {
                        try{
                            if(gridview_header_top_position == 0)
                            {
                                var jTableHead = $('.gridview_style tr th');
                                if(jTableHead.offset() != null)
                                    gridview_header_top_position = jTableHead.offset().top;
                            }

                            if ($(window).scrollTop() >= gridview_header_top_position) 
                            {
                                var jHeaderRow = $('table.gridview_style tr th');
                                if(jHeaderRow.attr('scrolling') == null || jHeaderRow.attr('scrolling') == 'false')
                                {
                                    if(divHeaderRow == null)
                                    {
                                        //Creating a div with jQuery
                                        divHeaderRow = jQuery('<div />');
                                        //Appending a Div
                                        jQuery(document.body).append(divHeaderRow);

                                        if(jHeaderRow.attr('orginal_height') == null) //Make sure the attribute is assigned in one time deal.
                                            jHeaderRow.attr('orginal_height', jHeaderRow.height());

                                        jHeaderRow.each(function() {
                                            var jHeaderCell = $(this);
                                            if(jHeaderCell.attr('orginal_left') == null) //Make sure the attribute is assigned in one time deal.
                                            {
                                                var left = jHeaderCell.offset().left;
                                                jHeaderCell.attr('orginal_left', left);
                                                jHeaderCell.attr('orginal_width', jHeaderCell.width());
                                            }
                                        });
                                        
                                        //After setting row to be fixed, the 'vertical-align' doesn't work because now the cell becomes a DIV rather than a <TD>
                                        //To fix this issue, must calculate the middle point and set the padding to that point.
                                        var padding_top = jHeaderRow.attr('orginal_height')/2;

                                        //Note that this above workaround should only apply if the header row's heigh is not large, then we cannot see the problem of vertical align

                                        jHeaderRow.each(function() {
                                            var jHeaderCell = $(this);

                                            //jHeaderCell.css('position', 'fixed');
                                            jHeaderCell.css('top', '0px');
                                            jHeaderCell.css('left', jHeaderCell.attr('orginal_left') + 'px');
                                            jHeaderCell.css('width', jHeaderCell.attr('orginal_width') + 'px');

                                            var clonedHeaderCell = jHeaderCell.clone();
                                            clonedHeaderCell.css('top', '0px');
                                            //clonedHeaderCell.css('left', jHeaderCell.attr('orginal_left') + 'px');
                                            
                                            var paddingLeft = $('.gridview_style tr th').css('padding-left').replace('px','');
                                            var paddingRight = $('.gridview_style tr th').css('padding-right').replace('px','');

                                            //Setting the width in accordance with different browser type (Flock, Safari)
                                            if(navigator.userAgent.toLowerCase().indexOf('flock') > 0)
                                                clonedHeaderCell.css('width', (jHeaderCell.width() - 2 + parseInt(paddingLeft) + parseInt(paddingRight)) + 'px');
                                            else
                                            if(navigator.userAgent.toLowerCase().indexOf('safari') > 0)
                                                clonedHeaderCell.css('width', (jHeaderCell.width() + 1 + parseInt(paddingLeft) + parseInt(paddingRight)) + 'px');

                                            //When adjusting the padding-top, the height is automatically resized.
                                            //To resolve it, must re-calculate the height.
                                            if(navigator.userAgent.toLowerCase().indexOf('flock') > 0)
                                                clonedHeaderCell.css('height', (parseInt(jHeaderRow.attr('orginal_height')) + 13) + 'px');
                                            else
                                            if(navigator.userAgent.toLowerCase().indexOf('safari') > 0)
                                                clonedHeaderCell.css('height', (parseInt(jHeaderRow.attr('orginal_height')) - padding_top + 13) + 'px');

                                            //Adjusting the padding-top to resolve the 'vertical-align' problem.
                                            clonedHeaderCell.css('padding-top', (padding_top - 2) + 'px');

                                            clonedHeaderCell.css('background-image', jHeaderCell.css('background-image'));
                                            clonedHeaderCell.css('background-repeat', jHeaderCell.css('background-repeat'));
                                            clonedHeaderCell.css('background-color', jHeaderCell.css('background-color'));

                                            clonedHeaderCell.css('text-align', jHeaderCell.css('text-align'));
                                            clonedHeaderCell.css('font-family', jHeaderCell.css('font-family'));
                                            clonedHeaderCell.css('font-size', jHeaderCell.css('font-size'));
                                            clonedHeaderCell.css('font-style', jHeaderCell.css('font-style'));
                                            
                                            var borderWidth = (jHeaderCell.outerWidth(false) - jHeaderCell.innerWidth())/2;
                                            clonedHeaderCell.css('border', 'solid ' + borderWidth + 'px gray');
                                            clonedHeaderCell.css('border-right','0px'); //Remove border-right to lessen the bold border among cells
                                            //clonedHeaderCell.css('border','solid 1px gray');

                                            divHeaderRow.append(clonedHeaderCell);
                                        });

                                        divHeaderRow.css('border-right','solid 1px gray'); //Don't forget to add the right-border for last cell

                                        jHeaderRow.parent().css('display','none');
                                        divHeaderRow.css('top', '0px');
                                        
                                        //Setting the Left in accordance with different browser type (Flock, Safari)
                                        if(navigator.userAgent.toLowerCase().indexOf('flock') > 0)
                                            divHeaderRow.css('left', ($('table.gridview_style').position().left - 1) + 'px');
                                        else
                                        if(navigator.userAgent.toLowerCase().indexOf('safari') > 0)
                                            divHeaderRow.css('left', $('table.gridview_style').css('left'));

                                        divHeaderRow.css('position', 'fixed');

                                        jHeaderRow.attr('scrolling', 'true');
                                    }
                                    divHeaderRow.css('display','');
                                }
                            }
                            else
                            {
                                var jHeaderRow = $('.gridview_style tr th');
                                jHeaderRow.attr('scrolling', 'false');    
                                jHeaderRow.parent().css('display','');

                                if(divHeaderRow != null)
                                    divHeaderRow.css('display','none');

                                jHeaderRow.css('position', ''); 
                            }
                        } catch(e) {alert(e);}
                    }
                    );
                }); 
            </script>
            ";

        ScriptManager.RegisterStartupScript(this, this.GetType(), "script_scroll", jQuery, false);
    }
    
    private void FreezeHeaderRowUponScroll()
    {
        //NOTE: This JQuery script must be inserted at code-bebind to make sure dynamic loading is running fine, otherwise it doesn't take into effect for the subsequent postbacks.
        string jQuery = @"
             <script type=""text/javascript"">
                var gridview_header_top_position = 0;

                $(document).ready(function() {
                    $(window).scroll(function() 
                    {
                        try{
                            var jHeaderRow = $('table.gridview_style tr th');
                            if(gridview_header_top_position == 0 && jHeaderRow.offset() != null)
                                gridview_header_top_position = jHeaderRow.offset().top;

                            if ($(window).scrollTop() >= gridview_header_top_position & 
                                $(window).scrollTop() <= (gridview_header_top_position + $('table.gridview_style').height())
                                )
                            {
                                //Persist the value of the left, the width, the height and save them in the attribute
                                if(jHeaderRow.attr('scrolling') == null || jHeaderRow.attr('scrolling') == 'false')
                                {
                                    //jHeaderRow.attr('orginal_vertical_align', jHeaderRow.css('vertical-align'));

                                    if(jHeaderRow.attr('orginal_height') == null) //Make sure the attribute is assigned in one time deal.
                                        jHeaderRow.attr('orginal_height', jHeaderRow.height());

                                    jHeaderRow.each(function() {
                                        var jHeaderCell = $(this);

                                        if(jHeaderCell.attr('orginal_left') == null) //Make sure the attribute is assigned in one time deal.
                                        {
                                            var left = jHeaderCell.offset().left;
                                            var browserInfo = navigator.userAgent.toLowerCase();

                                            //The overlapping border between adjacent cells occurs.
                                            //There is no absolute solution for that. Must apply the workaround for each browser.
                                            if(browserInfo.indexOf('firefox') > -1) //For FireFox
                                            {
                                                jHeaderCell.attr('orginal_left', left - 1); //Must substract 1 pixel for FireFox
                                                jHeaderCell.attr('orginal_width', jHeaderCell.width());
                                            }
                                            else
                                            if(browserInfo.indexOf('ie') > -1 || browserInfo.indexOf('chrome') > -1 || browserInfo.indexOf('opera') > -1)
                                            {
                                                jHeaderCell.attr('orginal_left', left);
                                                //For browswers Chrome, Opera: Add up 1 pixel for the header cell's width
                                                jHeaderCell.attr('orginal_width', jHeaderCell.width() + 1);
                                            }
                                            else
                                            {
                                                jHeaderCell.attr('orginal_left', left);
                                                jHeaderCell.attr('orginal_width', jHeaderCell.width());
                                            }
                                        }
                                    });
                                    
                                    //After setting row to be fixed, the 'vertical-align' doesn't work because now the cell becomes a DIV rather than a <TD>
                                    //To fix this issue, must calculate the middle point and set the padding to that point.
                                    var padding_top = jHeaderRow.attr('orginal_height')/2 - 6;
                                    //Note that this above workaround should only apply if the header row's heigh is not large, then we cannot see the problem of vertical align

                                    var widths = new Array();
                                    var index = 0;
                                    jHeaderRow.each(function() {
                                        var jHeaderCell = $(this);
                                        //var left = jHeaderCell.offset().left;

                                        jHeaderCell.css('position', 'fixed');
                                        jHeaderCell.css('top', '0px');

                                        //Restore the left, the width and padding-top after changing cell's position
                                        jHeaderCell.css('left', jHeaderCell.attr('orginal_left') + 'px');
                                        jHeaderCell.css('width', jHeaderCell.attr('orginal_width') + 'px');

                                        //Adjusting the padding-top to resolve the 'vertical-align' problem.
                                        jHeaderCell.css('padding-top', padding_top);

                                        widths[index] = (parseInt(jHeaderCell.attr('orginal_width')) + 0) + 'px';
                                        index = index + 1;
                                    });

                                    //When adjusting the padding-top, the height is automatically resized.
                                    //To resolve it, must re-calculate the height.
                                    jHeaderRow.css('height', (jHeaderRow.attr('orginal_height') - padding_top) + 'px');

                                    var lastHeaderCell = $('table.gridview_style tr th:last-child');
                                    if(lastHeaderCell.children().size() == 0)
                                        lastHeaderCell.append(""<img id='imgIcon' src='" + ResolveUrl("Images/icon_freeze_inactive.png") + @"' "" + 
                                        ""title='Grid Status: Header Row is freezing on top...'"" +  
                                        ""alt='Grid Status: Header Row is freezing on top...'"" +  
                                        ""></img>"");

                                    jHeaderRow.attr('scrolling', 'true');

                                    index = 0;
                                    $('table.gridview_style tr:last').find('td').each(function()
                                    {
                                        var jDataCell = $(this);
                                        if(widths[index] != undefined)
                                        {
                                            //jDataCell.parent().css('width', widths[index]);
                                            jDataCell.width(widths[index]);
                                            index = index + 1;
                                        }
                                    });
                                }
                            }
                            else
                            {
                                jHeaderRow.css('position', ''); //Put it back where the orginal position belongs
                                jHeaderRow.css('top', gridview_header_top_position + 'px');
                               
                                //Restore both 'height' and 'padding-top'     
                                if(jHeaderRow.attr('orginal_height') != undefined)
                                    jHeaderRow.css('height', jHeaderRow.attr('orginal_height') + 'px');
                                jHeaderRow.css('padding-top', '0px');

                                var lastHeaderCell = $('table.gridview_style tr th:last-child');
                                if(lastHeaderCell.children().size() > 0)
                                    lastHeaderCell.children().remove(); 

                                jHeaderRow.attr('scrolling', 'false');
                            }
                        } catch(e) {alert(e);}
                    });
                }); 
            </script>
            ";

        ScriptManager.RegisterStartupScript(this, this.GetType(), "script_scroll", jQuery, false);
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Chief Technology Officer Evizi
Vietnam Vietnam
I love working with Web-based workflow automation systems, ERP systems for SME, Data Visualization and Augmented Intelligence.

I have been working in ASP.NET for more than 12 years. I've also been working on Java and Windows-based apps for more than 5 years. My core competencies include ASP.NET (+net core), MVC, Restful API, Advance JavaScript, JQuery, Bootstrap, SubSonic, Dapper, Entity Framework, Lucne.net, ElasticSearch Ajax... I'm particularly interested in building smart apps with great UI/UX and high earned value.

I love to write elegant code. I am a type of pragmatic personality.

Recently, I've been interested in developing SPA apps using Angular with NodeJS backend. I also love to write the progressive web apps which could be the next big thing for the mobile web.

Feel free to discuss with me at: phamdinhtruong@gmail.com


Comments and Discussions