In Part 1 of this article we looked at the client-side JavaScript necessary for
maintaining scroll position across postbacks. In this second and final part, we'll see how to create a custom server control
to emit the proper JavaScript.
Generating the Appropriate JavaScript with a Custom Server Control
At this point we've seen what client-side JavaScript code is needed in order to persist scroll position across postbacks.
One option would be to manually add this JavaScript code to each and every page that needed to maintain scroll position.
However, by wrapping this markup in a custom, compiled server control, we can easily reuse this functionality by simply
plopping down the server control on those pages that need to utilize this functionality. To make this vision a reality, I
created a control called SmartScroller (whose code is available at the end of this article).
I decided to place my SmartScroller control in a namespace called sstchur.web.SmartNav. Since this control
does not have any visual display, but rather just emits JavaScript and a couple of hidden <input> fields, I
had SmartScroller derive from System.Web.UI.Control rather than System.Web.UI.WebControls.WebControl.
The following shell of my code shows this, along with SmartScroller's single member variable, m_theForm, which
we'll discuss in more detail shortly.
using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI;
namespace sstchur.web.SmartNav
{
public class SmartScroller : System.Web.UI.Control
{
private HtmlForm m_theForm = new HtmlForm();
public SmartScroller()
{
}
...
}
}
As you'll recall from our examination of the JavaScript code, we need to be able to reference the ID of the Web Form in
order to access the values of the hidden <input> fields in script. Therefore, from our server-side code
we need to be able to determine the Web Form's ID, meaning we have to first find the Web Form. To my knowledge, there's no
way to easily access the HtmlForm class instance in a WebForm short of recursing through the control hierarchy.
The SmartScroller control contains a GetServerForm() method that does just this:
private HtmlForm GetServerForm(ControlCollection parent)
{
foreach (Control child in parent)
{
Type t = child.GetType();
if (t == typeof(System.Web.UI.HtmlControls.HtmlForm))
return (HtmlForm)child;
if (child.HasControls())
return GetServerForm(child.Controls);
}
return new HtmlForm();
}
This method is called from the OnInit() method like so: m_theForm = GetServerForm(Page.Controls);
This will start searching for the HtmlForm starting from the top of the control hierarchy. Once it is found,
it is assigned to the private member variable m_theForm. (GetServerForm() works via a process
known as recursion; for more information on recursion be sure to read Recursion,
Why It's Cool.)
The next thing we need to do is override the OnInit() method, which is where we emit some Javascript code
responsible for handling the smart scrolling. You'll notice the JavaScript emitted in OnInit() is the same
as we examined earlier in this article.
protected override void OnInit(EventArgs e)
{
m_theForm = GetServerForm(Page.Controls);
HtmlInputHidden hidScrollLeft = new HtmlInputHidden();
hidScrollLeft.ID = "scrollLeft";
HtmlInputHidden hidScrollTop = new HtmlInputHidden();
hidScrollTop.ID = "scrollTop";
this.Controls.Add(hidScrollLeft);
this.Controls.Add(hidScrollTop);
string scriptString = @"
<!-- sstchur.web.SmartNav.SmartScroller
ASP.NET Generated Code -->
<script language = ""javascript"">
<!--
function sstchur_SmartScroller_GetCoords()
{
var scrollX, scrollY;
if (document.all)
{
if (!document.documentElement.scrollLeft)
scrollX = document.body.scrollLeft;
else
scrollX = document.documentElement.scrollLeft;
if (!document.documentElement.scrollTop)
scrollY = document.body.scrollTop;
else
scrollY = document.documentElement.scrollTop;
}
else
{
scrollX = window.pageXOffset;
scrollY = window.pageYOffset;
}
document.forms[""" + m_theForm.ClientID + @"""]." +
hidScrollLeft.ClientID + @".value = scrollX;
document.forms[""" + m_theForm.ClientID + @"""]." +
hidScrollTop.ClientID + @".value = scrollY;
}
function sstchur_SmartScroller_Scroll()
{
var x = document.forms[""" + m_theForm.ClientID +
@"""]." + hidScrollLeft.ClientID + @".value;
var y = document.forms[""" + m_theForm.ClientID +
@"""]." + hidScrollTop.ClientID + @".value;
window.scrollTo(x, y);
}
window.onload = sstchur_SmartScroller_Scroll;
window.onscroll = sstchur_SmartScroller_GetCoords;
window.onclick = sstchur_SmartScroller_GetCoords;
window.onkeypress = sstchur_SmartScroller_GetCoords;
// -->
</script>
<!-- End sstchur.web.SmartNav.SmartScroller ASP.NET Generated Code -->";
Page.RegisterStartupScript("SmartScroller", scriptString);
}
The first step in this function is to grab the server side form, and we do this by calling our recursive
GetServerForm() function, passing in the Page.Controls collection. This tells the
function to start looking for the server form at the top-most level of controls.
After we've got our server side form, we need to create two hidden <input> form fields to store
the coordinates of the scroll bar at any given moment. We assign logical IDs to them - scrollTop and
scrollLeft and then add them to our SmartScroller control collection.
The next step is to write the Javascript that handles the smart scrolling. The JavaScript code is stored in the variable
scriptString, which is then added to the page through a call to Page.RegisterStartupScript().
The Page class contains a number of methods to emit client-side script (such as RegisterStartupScript()).
For more information on injecting client-side script into a Web page from server-side code be sure to read
Scott Mitchell's article Working
With Client-Side Script.
That's all there is to the OnInit() method. There is one final piece of the puzzle that needs to be put into
place: the Render() method of the SmartScroller ASP.NET server control. This Render() method
simply ensures that the control is being rendered inside of a Web Form. It does this by calling the Page
class's VerifyRenderingInServerForm() method. This method checks to see if a Web Form exists on the page -
if it doesn't, it raises an exception.
Using the SmartScroller Control in an ASP.NET Web Page
Using SmartScroller in an ASP.NET Web page couldn't be easier. If you are using Visual Studio .NET, you can
simply add SmartScroller to the Toolbox by right-clicking on the Toolbox, selecting to Add/Remove Items, and browsing to
the SmartScroller assembly. If you are not using Visual Studio .NET, you'll need to add the SmartScroller assembly to
your Web application's /bin directory and then add a @Register directive to the top of your page that
looks like:
I've put all those <br> tags in just so the page will scroll for us. When visiting the
live demo, be sure to scroll down to one of the Button
Web controls and click it. Doing so will cause a postback, but you'll note that on postback your browser will retain the current
scroll position, rather than starting back up at the top of the page.
Conclusion
Having the browser retain its scrollbar position may not seem like a terribly important thing to spend your time and efforts
trying to accomplish, but trust me, it's really, really annoying when you click on a link of a question in a FAQ and expect
its answer, only to be launched way back to the top of the page. It's enough to make me say "forget it" and go some place
else to find my answer.
As we saw in this article, creating a custom server control allows for us to enable scroll retention by simply adding a
Web control to the page - all you have to do is plug it in to any server-side form, and you're good to go. That's pretty
minimal effort. If you're concerned about Javascript errors in browsers that may not support some of the functions we
used, you can also modify the code to suit your needs. I'm sure you can think of plenty of way to make this control more
robust. Feel free to email me with whatever you come up with (sstchur@yahoo.com)!
About Me
Stephen Stchur is a software development engineer with Microsoft and the creator of two open source project's on
Microsoft's CodePlex web site: WebSurvey and
The Gimme Javascript Library. Feel free to drop him and email with comments or
suggestions at sstchur@yahoo.com.