RadListBox Scrollbar Not Retaining Position Between Postbacks

6 posts, 0 answers
  1. Robert
    Robert avatar
    253 posts
    Member since:
    Aug 2007

    Posted 04 Jun 2013 Link to this post

    With much prior help from Telerik Support, I've implemented a fairly sophisticated RadListBox which let's the user single-click and double-click.  I thought everything was working perfectly until I discovered today that the vertical scrollbar is not maintaining its position between Postbacks.  This needs to be fixed.

    Here's the pertinent layout code:

    <asp:Panel ID="panelMain" runat="server" style="border-top: solid 1px #000000; padding-bottom:20px">
      <telerik:RadSplitter ID="radSplitter" runat="server" LiveResize="false">
        <telerik:RadPane ID="radPaneLeft" runat="server" Scrolling="None" MinWidth="232" MaxWidth="272">
          <telerik:RadSlidingZone ID="radSlidingZone" runat="server" Width="22" Height="0" ClickToOpen="true" DockedPaneId="radSlidingPane">
            <telerik:RadSlidingPane ID="radSlidingPane" runat="server" BackColor="#F0F8FF" CssClass="slidingPane" TabView="TextAndImage" IconUrl="~/Images/hierarchy.gif"
                                    DockOnOpen="true" OnClientDocked="radSlidingPane_Docked" OnClientUndocked="radSlidingPane_Undocked" Width="200">
     
              <telerik:RadListBox ID="radListBoxMuck" runat="server" Width="100%" Height="100%" OnClientSelectedIndexChanged="rlbClicked" OnClientItemDoubleClicked="rlbDoubleClicked"
                                  OnClientContextMenu="rlbContextMenu" onselectstart="return false;" OnItemCreated="radListBoxMuck_ItemCreated" EnableDragAndDrop="false">

    And here's the Javascript code that handles the RadListBox:

    var rlbClickTimer;  // Required presence for logic to distinguish between single & double-clicks
     
    function rlbClicked() {
      if (rlbClickTimer) clearTimeout(rlbClickTimer);
     
      rlbClickTimer = setTimeout(function () {
        // Code in 'rlbContextMenu' causes this method to be called.  When a right-click occurs,
        // we don't want a PostBack to occur so we're just going to exit quietly.
        var rlbRightClicked = $('input[id$=_radListBoxIsRightClicked]')[0].id;
        var hidVar = document.getElementById(rlbRightClicked);
        if (hidVar.value == 'true') {
          hidVar.value = ''// We successfully intercepted the right-click that got us to here so clear the hidden variable for when a left-click occurs
          return;
        }
     
        var updatePanel = $('div[id$=_UpdatePanel1]')[0].id;
        var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
        var slidingPane = slidingZone.getPanes()[0];
        var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
        var rlbIdxName = $('input[id$=_radListBoxIndex]')[0].id;
        hidVar = document.getElementById(rlbIdxName);
        var rlbIdxVal = hidVar.value;
     
        var parameters;
     
        if (rlbIdxVal == '')
          parameters = 'radListBoxMuck,' + listBox.get_selectedIndex();
        else {
          parameters = 'radListBoxMuck,' + rlbIdxVal;
          hidVar.value = '';
        }
     
        parameters += ',1';
     
        __doPostBack(updatePanel, parameters);
      }, 250);
     
      setTimeout(function() { scrollIntoView() }, 400);
    }
     
    // Works in conjunction with 'rlbClicked' and handles double-clicking on a RadListBox item.
    function rlbDoubleClicked() {
      var updatePanel = $('div[id$=_UpdatePanel1]')[0].id;
      var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
      var slidingPane = slidingZone.getPanes()[0];
      var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
      clearTimeout(rlbClickTimer);
     
      var parameters = 'radListBoxMuck,' + listBox.get_selectedIndex() + ',2';
     
      __doPostBack(updatePanel, parameters);
    }
     
    // Ensures that the selected item, if there is one, remains visible to the user after a postback.
    function scrollIntoView() {
      var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
      var slidingPane = slidingZone.getPanes()[0];
      var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
      var item = listBox.get_selectedItem();
      if (item != null)
        item.scrollIntoView();
    }

    Please note the last line of code, which calls the "scrollIntoView()" method of the RadListBoxItem.  I've stepped through the code and it appears to be running okay.  I've also confirmed that the "item" in question is always the correct one.  But it's as if "scrollIntoView()" isn't doing anything.

    Is there something wrong with my code?

    As an ancillary comment, I should mention that I've created a quite extensive function library that uses the AJAX Event Handler functions to maintain the position of any DIV scrollbar that I so identify during initialization.  It also works perfectly with your RadTreeView control.  But, when I tried using it with the RadListBox, it didn't work.  It relies on the fact that the "scrollTop" property of each DIV will range from '0' at its top position to a positive integer at the bottom.  So alternatively, if you could tell me how to adapt this code for the RadListBox then I'd also have a solution.  In other words, what RadListBox property maintains the scroll position?

    Hope you can help!

    Robert

  2. Nencho
    Admin
    Nencho avatar
    1457 posts

    Posted 07 Jun 2013 Link to this post

    Hello Robert,

    With the current usage of the scrollIntoView(), you are invoking the method on the object, which reference is accessed with var item = listBox.get_selectedItem(); However, the method should be invoked on the dom element, which could be accessed in the following manner :

    var item = listBox.get_selectedItem();
                if (item != null)
                    item.get_element().scrollIntoView();

    Hope this would help.


    Regards,
    Nencho
    Telerik
    If you want to get updates on new releases, tips and tricks and sneak peeks at our product labs directly from the developers working on the RadControls for ASP.NET AJAX, subscribe to the blog feed now.
  3. UI for ASP.NET Ajax is Ready for VS 2017
  4. Robert
    Robert avatar
    253 posts
    Member since:
    Aug 2007

    Posted 07 Jun 2013 Link to this post

    Nencho,

    I followed your advice.  At first, implementing your code didn't fix the problem but then I investigated a bit more and discovered something interesting:

    It turned out that the key to [kind of] getting ScrollIntoView to work had everything to do with the delay time that I attached to the timer in rlbClicked().  Here's the original line that launched the timer:

    setTimeout(function() { scrollIntoView() }, 400);

    With a 400ms delay, neither your code or my code worked.  But when I increased it to 1000ms then the ScrollIntoView function started to do something.  But what's most interesting is that with your code, the selected item would appear at the top of the visible RadListBox after the PostBack and with mine it would appear at the bottom of the visible RadListBox.

    So, while both approaches now work better than they did before, what I really want is for the selected item to stay precisely where it was before the PostBack.  To accomplish this I would like to utilize my AJAX EventHandler code that I described previously.  But to do that, I need to know what the equivalent to "scrollTop" is for the RadListBox.  Could you let me know?

    Robert
  5. Robert
    Robert avatar
    253 posts
    Member since:
    Aug 2007

    Posted 07 Jun 2013 Link to this post

    Nencho,

    A little further investigation revealed why my earliest attempts were not working and provided a solution that works perfectly.  I'm posting it here in the hopes that it'll help someone else in the future.

    If you recall, I couldn't figure out why restoring the 'scrollTop' value of the RadListBox [rendered as a DIV in HTML] wasn't working, since it had always worked with every other DIV I had ever used the technique on.  Then today I discovered the reason: It's not the main or outer DIV of the RadListBox that has the scrollbar, it's an inner one that is defined like this:

    <div class="rlbGroup rlbGroupRight">

    Realizing this, I modified my original code, which mostly keeps track of single & double clicks, to also save & restore the 'scrollTop' value of this inner DIV.  Voila, everything suddenly worked and now the selected RadListBoxItem stays precisely where it was before a PostBack.

    Here's the modified code, which other users will have to modify for their own application:

    var rlbClickTimer;  // Required presence for logic to distinguish between single & double-clicks
    var rlbScrollTop;   // Keeps track of the scroll position of the RadListBox before a PostBack occurs, so it can be restored after the PostBack
     
    function rlbClicked() {
      if (rlbClickTimer) clearTimeout(rlbClickTimer);
     
      rlbClickTimer = setTimeout(function () {
        // Code in 'rlbContextMenu' causes this method to be called.  When a right-click occurs,
        // we don't want a PostBack to occur so we're just going to exit quietly.
        var rlbRightClicked = $('input[id$=_radListBoxIsRightClicked]')[0].id;
        var hidVar = document.getElementById(rlbRightClicked);
        if (hidVar.value == 'true') {
          hidVar.value = ''// We successfully intercepted the right-click that got us to here so clear the hidden variable for when a left-click occurs
          return;
        }
     
        var updatePanel = $('div[id$=_UpdatePanel1]')[0].id;
        var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
        var slidingPane = slidingZone.getPanes()[0];
        var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
        var rlbIdxName = $('input[id$=_radListBoxIndex]')[0].id;
        hidVar = document.getElementById(rlbIdxName);
        var rlbIdxVal = hidVar.value;
     
        var parameters;
     
        if (rlbIdxVal == '')
          parameters = 'radListBoxMuck,' + listBox.get_selectedIndex();
        else {
          parameters = 'radListBoxMuck,' + rlbIdxVal;
          hidVar.value = '';
        }
     
        parameters += ',1';
     
        // Store the value of 'scrollTop' so we can restore it after the PostBack
        rlbScrollTop = $('div[id$=_radListBoxMuck] div.rlbGroup')[0].scrollTop;  //rlbScrollTop = listBox._scrollPosition;
     
        __doPostBack(updatePanel, parameters);
      }, 250); 
     
      // Note: Originally the delay was set to 400ms.  That didn't work.  Neither did 600ms.  So we'll stick with 1000ms, which seems to always work.
      setTimeout(function() { scrollIntoView() }, 1000);
    }
     
    // Works in conjunction with 'rlbClicked' and handles double-clicking on a RadListBox item.
    function rlbDoubleClicked() {
      var updatePanel = $('div[id$=_UpdatePanel1]')[0].id;
      var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
      var slidingPane = slidingZone.getPanes()[0];
      var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
      clearTimeout(rlbClickTimer);
     
      var parameters = 'radListBoxMuck,' + listBox.get_selectedIndex() + ',2';
     
      __doPostBack(updatePanel, parameters);
    }
     
    // Ensures that the selected item, if there is one, remains visible to the user after a postback.
    function scrollIntoView() {
      var slidingZone = $find($('table[id$=_radSlidingZone]')[0].id);
      var slidingPane = slidingZone.getPanes()[0];
      var listBox = $find($("#" + slidingPane.get_id() + " div[id$=_radListBoxMuck]")[0].id);
     
      var item = listBox.get_selectedItem();
      if (item != null) {
        // Note: Originally we used the 'scrollIntoView' function to preserve the position of the selected listbox item
        //       but it was not very precise.  The item did stay visible on the screen but was either at the very top or
        //       very bottom, depending on which version of the code we used.  So instead, we're restoring the 'scrollTop'
        //       value of the inner DIV of 'radListBoxMuck', which keeps the item precisely where it was before the PostBack.
        $('div[id$=_radListBoxMuck] div.rlbGroup')[0].scrollTop = rlbScrollTop;
      }
    }
  6. Nencho
    Admin
    Nencho avatar
    1457 posts

    Posted 12 Jun 2013 Link to this post

    Hello Robert,

    Thank you for sharing your implementation with the community. Your involvement is greatly appreciated.

    Regards,
    Nencho
    Telerik
    If you want to get updates on new releases, tips and tricks and sneak peeks at our product labs directly from the developers working on the RadControls for ASP.NET AJAX, subscribe to the blog feed now.
  7. Robert
    Robert avatar
    253 posts
    Member since:
    Aug 2007

    Posted 12 Jun 2013 Link to this post

    Nencho, you & your colleagues always go out of your way to help me.  I just try to give back to my fellow Telerik developers whenever I can.

    P.S. When my current project is finished (hopefully in a few months) someone in your company really should consider doing a White Paper on it because it makes extensive use of the Telerik suite of controls, providing functionality to the end users which simply wouldn't be possible with the native VStudio controls.
Back to Top
UI for ASP.NET Ajax is Ready for VS 2017