Save Scroll Position GridView Control

October 29, 2008 16:09 by wjchristenson2

I've seen a lot of articles on the web describing how to scroll a GridView.  Some of those articles may even go a little deeper and show you how to persist the scroll position across PostBacks.  What I haven't been able to find is how to encapsulate the scrolling and saving/persisting of the scroll position into a custom GridView control.  That is what I am going to show you how to do today.

There are many ways a developer may want to scroll their GridView (horizontal, vertical, freezing the header/footer, etc).  I'm not going to go into creating a bullet proof GridView control to accomplish those tasks.  My objective is to show you a technique to save/set the scroll position across PostBacks within a custom GridView control.

Here is what our end GridView control will look like.  First you can see a scrollable GridView.  When we scroll the GridView and then click the PostBack button, the page will perform a PostBack and the <div> will scroll itself back to where we left off.



So let's get to it.  First thing we do is create a new control and inherit from the GridView.  We then implement the IPostBackDataHandler to get and set our scroll position across PostBacks.  You'll see the LoadPostData function accomplishes this.  Take note that in line 3 I am referencing the name of my hidden field that I'll go over later.  We also tell the page that our new custom GridView (Me) has data to PostBack and we can do this in the GridView's Init.


   1:      Public Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean Implements System.Web.UI.IPostBackDataHandler.LoadPostData
   2:          'get and set the new scroll position
   3:          Dim postedValue As String = postCollection(Me.ClientID & "_scroll")
   4:          If Not postedValue Is Nothing Then
   5:              Dim presentValue As Integer = Me.ScrollPosition
   6:              Me.ScrollPosition = CType(postedValue, Integer)
   7:              Return Not presentValue.Equals(Me.ScrollPosition)
   8:          End If
   9:      End Function
  10:   
  11:      Public Sub RaisePostDataChangedEvent() Implements System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent
  12:          'do nothing
  13:      End Sub
  14:   
  15:      Private Sub ScrollingGridView_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
  16:          If Not Me.Page Is Nothing Then
  17:              'indicates that the control has data to postback
  18:              Me.Page.RegisterRequiresPostBack(Me)
  19:          End If
  20:      End Sub

In order to scroll my new custom GridView, I need to wrap my GridView with a <div> tag.  To do this, I can override the Render method like so:


   1:      Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
   2:          If Not Me.Page Is Nothing Then
   3:              Me.Page.VerifyRenderingInServerForm(Me)
   4:          End If
   5:   
   6:          Me.PrepareControlHierarchy()
   7:   
   8:          If Not Me.DesignMode Then
   9:              If String.IsNullOrEmpty(Me.ClientID) Then
  10:                  Throw New HttpException("ScrollingGridView must be parented!")
  11:              End If
  12:   
  13:              writer.AddAttribute(HtmlTextWriterAttribute.Id, String.Format("{0}_div", Me.ClientID), True)
  14:              writer.AddAttribute("onScroll", "saveScrollPos('" & String.Format("{0}_scroll", ClientID) & "', '" & String.Format("{0}_div", Me.ClientID) & "');")
  15:              writer.AddStyleAttribute(HtmlTextWriterStyle.OverflowY, "auto")
  16:              writer.AddStyleAttribute(HtmlTextWriterStyle.Height, "300px")
  17:              writer.RenderBeginTag(HtmlTextWriterTag.Div)
  18:          End If
  19:   
  20:          Me.RenderContents(writer)
  21:   
  22:          If Not Me.DesignMode Then
  23:              writer.RenderEndTag()
  24:          End If
  25:      End Sub

Notice that when I render my <div> that I also wire in a call to my JavaScript function to save the scroll position "onScroll".  At this point I have completed the PostBack data handling and I have shown you how to wrap a <div> around our GridView so we can scroll and it will call a JavaScript function "onScroll" that will save the position to a hidden field.

The next phase is to emit our JavaScripts.  I always emit JavaScript in the PreRender phase of a control's lifecycle as the ClientID should be set at that point.  We need a function to set the scroll position and a function to save the scroll position.  Here they are:


   1:          'generate & register javascript blocks
   2:          Dim key As String = "ScrollingGridView"
   3:          Dim script As StringBuilder = New StringBuilder()
   4:          With script
   5:              .AppendLine("function saveScrollPos(whereID, whatID) {")
   6:              .AppendLine("  document.getElementById(whereID).value = document.getElementById(whatID).scrollTop;")
   7:              .AppendLine("}")
   8:              .AppendLine("function setScrollPos(whereID, whatID) {")
   9:              .AppendLine("  document.getElementById(whatID).scrollTop = (document.getElementById(whereID).value.length > 0) ? document.getElementById(whereID).value : 0;")
  10:              .AppendLine("}")
  11:          End With
  12:          If Not sm Is Nothing Then
  13:              ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
  14:          Else
  15:              Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
  16:          End If

Notice we either acquire or save the scroll position to our hidden field.  Now how do we emit a hidden field inside our GridView?  My first attempts were to override the Render of the GridView control and emit hidden field there.  It didn't work.  I ended up registering the hidden field via .NET's scripting objects instead.  Check it out:


   1:          'register our hidden field to save our scroll position
   2:          If Not sm Is Nothing Then
   3:              ScriptManager.RegisterHiddenField(Me.Page, String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
   4:          Else
   5:              Me.Page.ClientScript.RegisterHiddenField(String.Format("{0}_scroll", Me.ClientID), Me.ScrollPosition.ToString())
   6:          End If

Notice that I assign the scroll position to the hidden field's value.  Last thing we need to do is register a startup script to get the persisted scroll position from our hidden field and scroll our <div>.  If you remember we created a JavaScript function for this (setScrollPosition).


   1:          'generate & register startup javascripts
   2:          key = String.Format("{0}_setScrollPos", Me.ClientID)
   3:          script = New StringBuilder()
   4:          script.AppendLine("setScrollPos('" & String.Format("{0}_scroll", Me.ClientID) & "','" & String.Format("{0}_div", Me.ClientID) & "');")
   5:          If Not sm Is Nothing Then
   6:              ScriptManager.RegisterStartupScript(Me.Page, Me.GetType(), key, script.ToString(), True)
   7:          Else
   8:              Me.Page.ClientScript.RegisterStartupScript(Me.GetType(), key, script.ToString(), True)
   9:          End If

Putting it all Together:

In summary, we persist the scroll position of the div that wraps our GridView via a hidden field which is registered via the Page.ClientScript or ScriptManager object.  We wrap the GridView with a <div> by overriding the GridView's Render method.  The <div> saves the scroll position to the hidden field as the <div> is scrolled.  We acquire the value stored in the hidden field via the LoadPostData function.  Once a PostBack occurs, we set the scroll position of the <div> with a startup script.


ScrollingGridView_Soln.zip (97.19 kb)

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Show UpdateProgress when using an UpdatePanel with Triggers

August 23, 2008 14:05 by wjchristenson2

with ASP.NET AJAX Extensions 1.0 for ASP .NET 2.0...

You may have come across a situation where an UpdatePanel performs a partial-page update and the associated UpdateProgress control does not display.  The culprit is UpdatePanel AsyncPostbackTriggers.  If an UpdatePanel's partial-page update was triggered by a control outside the UpdatePanel, the UpdateProgress control is oblivious to this fact.  The workaround that I've found is to get a handle to the instance of the PageRequestManager class and add our own events to show and hide the UpdateProgress ourselves via JavaScript before and after the triggered request.

We are going to create a page that will add 2 numbers and show the calculated result.  We will have a TextBox for number 1, a TextBox for number 2, and a label to show the result all within an UpdatePanel.  The calculate Button will be outside the UpdatePanel and will thus be our AsyncPostBackTrigger.  We then have our UpdateProgress control that we want shown while the webpage calculates our results.  Here is the HTML markup to accomplish our base page.

 

   1:          <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
   2:            <Triggers>
   3:              <asp:AsyncPostBackTrigger ControlID="Button1" EventName="Click" />
   4:            </Triggers>
   5:            <ContentTemplate>
   6:              <asp:TextBox ID="tbxValue1" runat="server" Text="1" Width="50" /> + <asp:TextBox ID="tbxValue2" runat="server" Text="1" Width="50" /> = <asp:Label ID="lblResult" runat="server" Text="2" />
   7:            </ContentTemplate>
   8:          </asp:UpdatePanel>
   9:          
  10:          <asp:Button ID="Button1" runat="server" Text="Calculate" />
  11:          
  12:          <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1" DynamicLayout="true" DisplayAfter="0">
  13:            <ProgressTemplate>
  14:              <div style="background-color: #ffffc9;">Calculating...<div>
  15:            </ProgressTemplate>
  16:          </asp:UpdateProgress>

Now that we have our markup finished, let's handle the click event of our calculate button.  To see the UpdateProgress control for a longer duration, I went ahead and added a sleep timer of 3 seconds to delay the calculation.

 

   1:      Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
   2:          System.Threading.Thread.Sleep(3000)
   3:          lblResult.Text = (CInt(tbxValue1.Text) + CInt(tbxValue2.Text)).ToString()
   4:      End Sub

Now for the actual meat of this post.  The following JavaScript calls the getInstance() method of the PageRequestManager object to get the instance of the PageRequestManager class.  We then add our functions to be called when the the request is initialized and ended.  This will allow us to show our UpdateProgress control when the request is initialized and then hide it again when the request is ended.  Here is the JavaScript to accomplish this.

 

   1:  <script type="text/javascript" language="javascript">
   2:  <!-- 
   3:   
   4:  var prm = Sys.WebForms.PageRequestManager.getInstance();
   5:  var postBackElement;
   6:   
   7:  function CancelAsyncPostBack() {
   8:    if (prm.get_isInAsyncPostBack()) {
   9:      prm.abortPostBack();
  10:    }
  11:  }
  12:   
  13:  prm.add_initializeRequest(InitializeRequest);
  14:  prm.add_endRequest(EndRequest);
  15:   
  16:  function InitializeRequest(sender, args) {
  17:    if (prm.get_isInAsyncPostBack()) {
  18:        args.set_cancel(true);
  19:    }
  20:    postBackElement = args.get_postBackElement();
  21:    if (postBackElement.id == 'Button1') {
  22:      $get('UpdateProgress1').style.display = 'block'; 
  23:    }
  24:  }
  25:  function EndRequest(sender, args) {
  26:    if (postBackElement.id == 'Button1') {
  27:      $get('UpdateProgress1').style.display = 'none';
  28:    }
  29:  }
  30:   
  31:  // -->
  32:  </script>

 

Note that we acquire what element fired the request.  We only want to show/hide our UpdateProgress control if the postBackElement was our calculate button.

 

TriggersUpdateProgress_Soln.zip (45.30 kb)


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5