Test the online demo of ScrollingGrid
: Online Demo^ (supports Internet Explorer, Firefox 1.0+, Netscape 7+).
Introduction
This control provides a cross-browser solution to a common problem with large DataGrid
s: 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 :-).
Advantages over similar controls
- Cross-browser compatible: Mozilla Firefox 1+, Internet Explorer 5+, Netscape 7+.
<select>
elements do not float above the header. This is a common complaint with other solutions.- Last scroll position is submitted on postback, which you can then use to set the start scroll position.
- You still use the ASP.NET
DataGrid
control, so you don't lose Intellisense in Visual Studio's HTML editor (for grid columns etc.).
Disadvantages
- It only freezes header and bottom-pager rows. It does not freeze columns (similar IE-only controls can).
Other points
- Simple implementation.
- Supports multiple scrolling grids on the same page.
- Doesn't work with Opera browser (yet).
- If JavaScript is disabled, the entire original
DataGrid
will scroll within the mainDIV
(i.e., the header doesn't freeze).
Using the ScrollingGrid
Visual Studio designer
- Download and extract the DLL zip file to the root of your web project: Collapse Copy Code
bin/ScrollingGrid.dllScrollingGrid.js
- Add the
ScrollingGrid
control to your toolbox by browsing to bin/ScrollingGrid.dll. - Create a
ScrollingGrid
control on your page. - Drag a
DataGrid
into theScrollingGrid
control. - If your page is not in the root of your web project, you will need to specify the
ScriptPath
property. E.g.,ScriptPath="../"
.
Visual Studio HTML view
- Download and extract the DLL zip file to the root of your web project: Collapse Copy Code
bin/ScrollingGrid.dllScrollingGrid.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).
- In your .aspx page:
- Register the
TagPrefix
:Collapse Copy Code<%@ Register TagPrefix=avg Assembly=ScrollingGrid Namespace=AvgControls %>
- Within your web form, surround your
DataGrid
control with theScrollingGrid
control as follows:Collapse Copy Code<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>
- If your page is not in the root of your web project, you will need to specify the
ScriptPath
property. E.g.,ScriptPath="../"
.
- Register the
ScrollingGrid notes
- The
Width
,Height
,ScriptPath
, andCssClass
properties are all optional and have default values. - The
Width
value may be pixels or percentage. - The
Height
value must be pixels (not percentage), and corresponds to the height of theDIV
that contains the data rows. Since the header and pager are moved outside thisDIV
, your total height will be slightly larger. - The
ScrollingGrid
expects only theDataGrid
as a child control. - If your
DataGrid
contains a bottom pager, it will be automatically frozen underneath the content rows. - If your
ScrollingGrid
is not contained within a web form, you should reference the JavaScript file in yourHEAD
tag:Collapse Copy Code<script language="JavaScript" src="ScrollingGrid.js"></script>
- Some properties cannot be changed at runtime because the
ScrollingGrid
creates the control structure in theOnInit
method (seemed to be the only way theDataGrid
postback functionality could be preserved). E.g., theDataGrid
'sShowHeader
and theScrollingGrid
'sScrollingEnabled
property. - Images in your
DataGrid
may get clipped in Internet Explorer if you don't set theirwidth
attribute. - If you use the Visual Studio .NET designer, you can add this control to your toolbox. Then, just drag it to the page, and drag your
DataGrid
into theScrollingGrid
.
DataGrid notes
There are a couple of points to be aware of when it comes to your DataGrid
control:
- If you do not set the
CellPadding
attribute of yourDataGrid
, theScrollingGrid
control will assign a value of 2. The default value of -1 causes problems in Firefox. - The
ScrollingGrid
control automatically assignsGridLines=None
on yourDataGrid
control; otherwise, Firefox will ignore yourCellSpacing
value. This is a common problem with theDataGrid
in Firefox. - The
ScrollingGrid
control automatically sets yourDataGrid
'sBorderWidth=0
property; otherwise, Firefox doesn't match up the columns accurately. If you need to display borders on yourDataGrid
table, I recommend setting theCellSpacing
property to the width of the border (and must also haveGridLines=None
). Then, set yourScrollingGrid
'sBackColor
property (which will show through as the border color).
ScrollingGrid class
Summary
Cross-browser container control for a DataGrid
to freeze its header and bottom pager while scrolling both horizontally and vertically.
Syntax
public class ScrollingGrid : System.Web.UI.WebControls.Panel
Members (Excluding inherited)
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.
|
Inherited properties
Only these inherited properties have any effect on the HTML output:
BackColor
CssClass
Height
Width
How it works
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 DIV
s. 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).
The code
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 = d0cument.getElementById(scrollingGridID + "$tblHdr");var tblDataGrid = d0cument.getElementById(gridID);var tblPager = d0cument.getElementById(scrollingGridID + "$tblPager");// get header table's first rowvar tbodyEl = tblHdr.childNodes[firstChildElIndex(tblHdr, "TBODY")];var trEl = tbodyEl.childNodes[firstChildElIndex(tbodyEl, "TR")];// get datagrid table's first rowvar tbodyEl2 = tblDataGrid.childNodes[firstChildElIndex(tblDataGrid, "TBODY")];var trEl2 = tbodyEl2.childNodes[firstChildElIndex(tbodyEl2, "TR")];// delete empty TR on placeholder tabletbodyEl.removeChild(trEl);// move the header row from datagrid table to our placeholder tabletbodyEl.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 (!d0cument.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 (d0cument.getElementById(scrollingGridID + "$divHdr") != null) d0cument.getElementById(scrollingGridID + "$divHdr").scrollLeft = divObj.scrollLeft; // save scroll position to hidden input d0cument.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.
Issues
- In Firefox (versions prior to 1.5), if you drag to select text in the content table and cause the content
DIV
to scroll, the scroll event handler does not fire and so the header doesn't get sync'd. - In Firefox (versions prior to 1.5), the mouse wheel doesn't work with
DIV
s. This is a browser behaviour. - To disable the scrolling behaviour, the
ScrollingEnabled
property of theScrollingGrid
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 theOnInit
method (which is necessary to avoid issues with theDataGrid
's postback events). I'll have to experiment with a few other ideas to work around this limitation.
Points of interest
- The rendering behaviour of IE vs. FF is very different. Developing the variable width functionality was much easier with Firefox which renders tables as you would expect when specifying percentage widths. However, IE reacted completely differently. Because the
DIV
s 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 usetable-layout:fixed
which tells IE to listen to the specified table widths (and clip any wide content). I then use the script to update theDIV
widths to the same width as theTD
. - Large amounts of data don't seem to present any problems. The header-row doesn't sync quite as instantly when there are thousands of rows. And the browser takes up a lot of memory and CPU. But from what I could tell, no more so than using a
DataGrid
on its own. - Developing the DHTML to work with Firefox + IE was a challenge, primarily because the Firefox DOM creates
#text
nodes even when there is only whitespace between HTML tags. - If you want to clip the text in a column (and not have it wrap), you need to create a
TemplateColumn
and do some fancy stuff with CSS and aDIV
. 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 theTemplateColumn
to achieve this:Collapse Copy Code<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>
Conclusion
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.
Updates
August 2006 - Major improvements to the control. Reworked this article.
- Now supports variable width (i.e., percentage).
- Submits the last scroll position on postback, which can optionally be used to set the start scroll position (using the new
StartScrollPos
property). - Utility function to scale the height when the browser is resized.
- Added a property to specify the path to ScrollingGrid.js.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author
'ASP.NET' 카테고리의 다른 글
HTML5 데모 사이트들 (0) | 2012.05.21 |
---|---|
AJAX에서 즐겨찾기와 뒤로가기를 다루는 방법 (0) | 2010.07.01 |
.NET Smart client -2- (Hello SmartClient) (0) | 2009.04.15 |
How to make Apache run ASP.NET / ASP.NET 2.0 (0) | 2009.04.15 |
테이블에 스크롤 바 넣는 방법 (0) | 2009.04.15 |