When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, November 17, 2004

Maintaining Scroll Position on Postback, Part 2

By Steve Stchur


  • Read Part 1

  • 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.

    protected override void Render(HtmlTextWriter writer)
    {
       Page.VerifyRenderingInServerForm(this);
       base.Render(writer);
    }
    

    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:

    <%@ Register TagPrefix = "sstchur" Namespace = "sstchur.web.SmartNav" Assembly = "sstchur.web.smartnav" %>

    Then, in your ASP.NET page, add the following Web control somewhere inside of your Web Form:

    <sstchur:SmartScroller runat = "server" />

    A complete example (and live demo) can be seen below:

    <%@ Register TagPrefix = "sstchur" Namespace = "sstchur.web.smartnav" 
                 Assembly = "sstchur.web.smartnav" %>
    <%@ Page Language = "C#" %>
    
    <html>
       <body>
          <form runat = "server">
             
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             
             <asp:Button Text = "Click Me" runat = "server" />
             
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             <br><br><br><br><br><br><br><br><br><br><br><br><br>
             
             <asp:Button Text = "Click Me" runat = "server" />
             
             <sstchur:SmartScroller runat = "server" />
          </form>
       </body>
    </html>
    
    [View a Live Demo!]

    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)!

    Happy Programming!

  • By Steve Stchur


    Attachments


  • Download the complete SmartScroller code (in ZIP format)

    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.



  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article