![]() |
Web Development »
ASP.NET Controls »
General
Intermediate
License: The Code Project Open License (CPOL)
ScrollingGrid: A cross-browser freeze-header two-way scrolling DataGridBy Ashley van GervenA cross-browser container control for a DataGrid to freeze the header row and sync the header when the DataGrid is scrolled horizontally. |
C#, Javascript.NET 1.1, Win2K, WinXP, ASP.NET, ADO.NET, VS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

Test the online demo of ScrollingGrid: Online Demo^ (supports Internet Explorer, Firefox 1.0+, Netscape 7+).
This control provides a cross-browser solution to a common problem with large DataGrids: being able to scroll data while keeping the header row frozen above the data. This control allows two-way scrolling - i.e., the header slides left and right as you scroll horizontally.
There is an IE-only solution that has been around for some time, which makes the header row behave like a layer. But one minor issue with it is that dropdown lists float above the header row when scrolling. A major issue with it is that it's IE-only :-).
<select> elements do not float above the header. This is a common complaint with other solutions.
DataGrid control, so you don't lose Intellisense in Visual Studio's HTML editor (for grid columns etc.). DataGrid will scroll within the main DIV (i.e., the header doesn't freeze). bin/ScrollingGrid.dll
ScrollingGrid.js
ScrollingGrid control to your toolbox by browsing to bin/ScrollingGrid.dll.
ScrollingGrid control on your page.
DataGrid into the ScrollingGrid control.
ScriptPath property. E.g., ScriptPath="../". bin/ScrollingGrid.dll
ScrollingGrid.js
Now add a reference to ScrollingGrid.dll in your web project. Alternatively, you can add ScrollingGrid.cs to your project (included in the source ZIP), instead of using ScrollingGrid.dll (which you should delete in this case).
TagPrefix: <%@ Register TagPrefix=avg Assembly=ScrollingGrid
Namespace=AvgControls %>
DataGrid control with the ScrollingGrid control as follows: <form runat="server">
<avg:ScrollingGrid runat="server" ID=sg1
Width=450 Height=240 CssClass=sgTbl>
<asp:DataGrid runat="server" ID=Grid2 CellPadding=5 CellSpacing=1
AutoGenerateColumns=True AllowSorting=True
AllowPaging=True PageSize=35
OnPageIndexChanged=Grid2_PageIndexChanged
AllowCustomPaging=True>
<HeaderStyle BackColor=red ForeColor=white Font-Bold=True />
<ItemStyle BackColor=#fefefe />
<AlternatingItemStyle BackColor=#eeeeee />
<PagerStyle BackColor=silver ForeColor=White
Mode=NumericPages />
</asp:DataGrid>
</avg:ScrollingGrid>
</form>
ScriptPath property. E.g., ScriptPath="../". Width, Height, ScriptPath, and CssClass properties are all optional and have default values.
Width value may be pixels or percentage.
Height value must be pixels (not percentage), and corresponds to the height of the DIV that contains the data rows. Since the header and pager are moved outside this DIV, your total height will be slightly larger.
ScrollingGrid expects only the DataGrid as a child control.
DataGrid contains a bottom pager, it will be automatically frozen underneath the content rows.
ScrollingGrid is not contained within a web form, you should reference the JavaScript file in your HEAD tag: <script language="JavaScript" src="ScrollingGrid.js"></script>
ScrollingGrid creates the control structure in the OnInit method (seemed to be the only way the DataGrid postback functionality could be preserved). E.g., the DataGrid's ShowHeader and the ScrollingGrid's ScrollingEnabled property.
DataGrid may get clipped in Internet Explorer if you don't set their width attribute.
DataGrid into the ScrollingGrid. There are a couple of points to be aware of when it comes to your DataGrid control:
CellPadding attribute of your DataGrid, the ScrollingGrid control will assign a value of 2. The default value of -1 causes problems in Firefox.
ScrollingGrid control automatically assigns GridLines=None on your DataGrid control; otherwise, Firefox will ignore your CellSpacing value. This is a common problem with the DataGrid in Firefox.
ScrollingGrid control automatically sets your DataGrid's BorderWidth=0 property; otherwise, Firefox doesn't match up the columns accurately. If you need to display borders on your DataGrid table, I recommend setting the CellSpacing property to the width of the border (and must also have GridLines=None). Then, set your ScrollingGrid's BackColor property (which will show through as the border color). Cross-browser container control for a DataGrid to freeze its header and bottom pager while scrolling both horizontally and vertically.
public class ScrollingGrid : System.Web.UI.WebControls.Panel
|
|
FirefoxBorderWorkaround |
Set to false if
|
|
|
FooterWidthReduction |
Get/set pixel width to reduce the footer by
|
|
|
HeaderWidthReduction |
Get/set pixel width to reduce the header by, e.g., 17 = scrollbar width (if you don't want the header to extend across the top of the scrollbar)
|
|
|
OnInit(EventArgs) |
Creates the controls before and after the child
|
|
|
Overflow |
Content
|
|
|
RenderBeginTag(HtmlTextWriter) |
Output's start of control's container TABLE.
|
|
|
RenderEndTag(HtmlTextWriter) |
Output's end of control's container TABLE
|
|
|
ScriptPath |
Get/set the location of ScrollingGrid.js
|
|
|
ScrollingEnabled |
Set to false to display the
|
|
|
SetStartScrollPosFromPostack() |
Set starting scroll position of content
|
|
|
StartScrollPos |
Get/set the start scroll position of the content DIV.
|
Only these inherited properties have any effect on the HTML output:
BackColor
CssClass
Height
Width I had the initial idea after being presented with a question at a job interview, a few years ago. The idea being that the header could actually be an entirely separate table, with the column widths matching exactly with the content table. Both tables would be in their own DIVs. The header DIV would hide the overflow. The content DIV would scroll. When the user scrolls the content DIV across, the header DIV is automatically scrolled to the same horizontal scroll-value. And when the user scrolls the content DIV down, the header remains in view.
However, manually setting column widths is not practical. And making a new grid control would have limited appeal, since most developers are used to the functionality of the DataGrid control. It was about a year ago that I started working on the idea of rendering it around the DataGrid control. But one problem is that the DataGrid doesn't give you a way to get the header HTML only. But accessing the header row in the browser's DOM is simple enough. So as the page is loading in the browser, the script simply reassigns the header TR to a new table.
But moving the header row does not keep the original column widths. So, they then need to be dynamically matched up. Once that is done, it pretty much looks like the original table, with scrollbars for the data rows, and a "frozen" header row that slides left and right as the data is scrolled.
As for the ASP.NET control, the ScrollingGrid class inherits from the Panel control in order to be VS.NET designer-friendly. However, the HTML output is completely custom, so most of the Panel's inherited properties have no effect. The ScrollingGrid expects a DataGrid child-control, and adds its HTML around the DataGrid. The main reason I did not inherit from the DataGrid class is that you would lose VS.NET Intellisense when coding the DataGrid in HTML mode (quite frustrating if you code ASP.NET pages in HTML mode).
Most of this control's functionality is in the JavaScript initialisation of the control in the browser. The DataGrid's header row and bottom pager row are moved to their respective place-holder tables. Then, the header and content column widths are sync'd by increasing the width of the narrowest column.
Column widths are often influenced by the total width of the table. In Internet Explorer, you can change this behaviour by setting tableEl.style.tableLayout = "fixed". However, in Firefox, this doesn't seem to have any effect, so instead, you need to make sure the table has plenty of room to expand. This is accomplished by setting the width of the outer place-holder table extremely wide (i.e., 10000).
Here is an excerpt from the initScrollingGrid() JavaScript function (to move the header TR element to the place-holder table):
var tblHdr = document.getElementById(scrollingGridID + "$tblHdr");
var tblDataGrid = document.getElementById(gridID);
var tblPager = document.getElementById(scrollingGridID + "$tblPager");
// get header table's first row
var tbodyEl = tblHdr.childNodes[firstChildElIndex(tblHdr, "TBODY")];
var trEl = tbodyEl.childNodes[firstChildElIndex(tbodyEl, "TR")];
// get datagrid table's first row
var tbodyEl2 = tblDataGrid.childNodes[firstChildElIndex(tblDataGrid, "TBODY")];
var trEl2 = tbodyEl2.childNodes[firstChildElIndex(tbodyEl2, "TR")];
// delete empty TR on placeholder table
tbodyEl.removeChild(trEl);
// move the header row from datagrid table to our placeholder table
tbodyEl.appendChild(trEl2);
The firstChildElIndex function is a necessary step for Firefox, in response to an annoying behaviour - namely that white-space results in a "#text" childNode in the DOM tree. So in some cases, TBODY is the first childNode of TABLE, and in other cases, the second childNode (depending on whether there is white-space between <table> and <tr>).
Here is an excerpt from the SetWidths JavaScript function:
for (var i=0; i<widths.length; i++)
{
if (widths[i]+"" == "undefined")
continue;
// TD element for the header row
var tdHdr = trEl.childNodes[i];
// TD element for the content row
var tdContent = trEl2.childNodes[i];
var widthAdjustment = 0;
if (!document.all)
{
// FF: subtract cellpadding
widthAdjustment = -2 * parseInt(tblGrid.getAttribute("cellpadding"));
}
// Update either the header cell or content cell
// (not both, otherwise FF stuffs up)
if (tdHdr.offsetWidth != widths[i])
// update header column width
tdHdr.style.width = widths[i] + widthAdjustment;
if (tdContent.offsetWidth != widths[i])
// update content column width
tdContent.style.width = widths[i] + widthAdjustment;
}
The widths array is populated in a previous loop, and contains the correct width for each column. So here, the appropriate table cells are adjusted to their new width.
To sync the header with the content, this simple JavaScript function handles scroll events on the content DIV:
// content scroll event handler (matches the header row
// with the horizontal scroll position of content)
function updateScroll(divObj, scrollingGridID)
{
if (document.getElementById(scrollingGridID + "$divHdr") != null)
document.getElementById(scrollingGridID + "$divHdr").scrollLeft =
divObj.scrollLeft;
// save scroll position to hidden input
document.getElementById(scrollingGridID + "$hdnScrollPos").value =
divObj.scrollLeft + "-" + divObj.scrollTop;
}
Even though the header DIV does not display a scrollbar, its scrollLeft property still shifts the position of its content.
DIV to scroll, the scroll event handler does not fire and so the header doesn't get sync'd.
DIVs. This is a browser behaviour.
ScrollingEnabled property of the ScrollingGrid has to be specified in the control's server tag (not code-behind). Setting this at runtime doesn't work. This is a side-effect of setting all the controls in the OnInit method (which is necessary to avoid issues with the DataGrid's postback events). I'll have to experiment with a few other ideas to work around this limitation. DIVs within the outer table contained a lot of content (even though the overflow was clipped), IE was making the outer table really wide to accommodate the content. The solution was to use table-layout:fixed which tells IE to listen to the specified table widths (and clip any wide content). I then use the script to update the DIV widths to the same width as the TD.
DataGrid on its own.
#text nodes even when there is only whitespace between HTML tags.
TemplateColumn and do some fancy stuff with CSS and a DIV. Firefox scores extra points here because you can actually highlight the text and it will slide across within the clipped area, whereas IE just clips it. Here is the TemplateColumn to achieve this: <asp:TemplateColumn Visible=True HeaderText=ShipName>
<ItemTemplate>
<div style="overflow:hidden; text-overflow:clip; width:70px;">
<nobr><%# DataBinder.Eval(Container.DataItem, "ShipName") %></nobr>
</div>
</ItemTemplate>
</asp:TemplateColumn>Developing cross-browser DHTML can be a real challenge, but in my opinion, Firefox is a great browser, and its continued popularity warrants the extra effort. It's definitely been a good lesson in browser rendering behaviours as well as developing custom controls. Feel free to leave feedback below if you find this control useful.
August 2006 - Major improvements to the control. Reworked this article.
StartScrollPos property).
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 28 Aug 2006 Editor: Smitha Vijayan |
Copyright 2005 by Ashley van Gerven Everything else Copyright © CodeProject, 1999-2009 Web10 | Advertise on the Code Project |