Grid Scroll programatically not working

10 posts, 0 answers
  1. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 13 Aug Link to this post

    Hi,

    I spent days about that without finding any solution. 

    The case is simple :

    I have a Grid using virtual scrolling and a "nav toolbar" with buttons : "First, Previous, Next, Last".

    The nav toolbar purpose is to navigate through the records contained in the grid.  

    Note that the grid itself is in a kendo TabStrip but at this point, I use the nav toolbar only when the grid is visible. Later, I'll need to resync the grid if the user use the toolbar when the tab ontaining the grid is hidden, but it's another story.

     

    I tried several approaches, here is the last.

    Working code for "First" button : In any case, I get the first record selected at the top of the grid 

    first : function () {
                if(oTab.oGrid.dataSource.page() != 1){
                    gotoFirstRow=true;  // => the select will occur in dataBound event       
                    oTab.oGrid.dataSource.page(1);
                }
                else {
                    oTab.oGrid.select("tr:eq('0')");
                }
            }

     

    In databound this code works, simple :

    ...
         if (gotoFirstRow){
    gotoFirstRow=false;                
    oTab.oGrid.select("tr:eq('0')");
     }

    ...

     

    The same kind of code works for the "Last" button.

     

    For the "Next" (and "Prev") button, it works untill the grid needs to load data :

    next : function () {

                    // The uidNext is set in change event : When a record is selected, I populate

                    // uidCurrent/idproCurrent for the current record

                    // uidNext/idproNext for the next record if any (or null if no more record)

                    // uidPrev/idproPrev for the previous record if any (or null if no more record)
                    // idpro is a unique field idnetifier all records have
                
    if (typeof uidNext == "undefined"){ // For example when the current selected record is the last one
                    return;
                }
                idproToSelect=idproNext; // => So the select will occur in dataBound event.    
                 
                oTab.oGrid.select("tr[data-uid='"+uidNext+"']");           
                 

                            // Now trying to put the selected record at the top of the grid. The goal is that if user use the nav bar, the grid always presents the

                           // record at the top

                var scrollContentOffset = oTab.oGrid.element.find("tbody").offset().top;
                var selectContentOffset = oTab.oGrid.select().offset().top;
                var distance = selectContentOffset - scrollContentOffset;
                oTab.oGrid.wrapper.find(".k-scrollbar-vertical").scrollTop(distance);
    }

     

    When I use scrollTop, if Kendo needs more records, it loads them. So I continue the process in dataBound event :

     

    A this point, nothing works at all. This this the code in databound event:

    if (idproToSelect != null){
                         
                 // After new records are loaded, I search a record matching the unique field idproToSelect assigned previously
    oTab.oGrid.element.find("td[data-field='idpro']").each(function(i, item) {
           if($(item).html() == idproToSelect) {

               // When I found it, I get the uid in order to select the row

              uidToSelect=$(item).parent().attr('data-uid');
           }
    });

          // The row is selected

    oTab.oGrid.select("tr[data-uid='"+uidToSelect+"']");
    // RAZ
    uidToSelect=null;
     idproToSelect=null;

     

    // Now trying to put selected row at the top of the grid : not working

    var scrollContentOffset = oTab.oGrid.element.find("tbody").offset().top;
           var selectContentOffset = oTab.oGrid.select().offset().top;
           var distance = selectContentOffset - scrollContentOffset;
           oTab.oGrid.wrapper.find(".k-scrollbar-vertical").scrollTop(distance);

     }

    The problem i that grid nehavior becomes erratic, the scroll position is not right, so most of the time, I think the right record is selected, but it isn't showed. I suspect a problem with scrollTop when used form databound event. 

     

  2. Preslav
    Admin
    Preslav avatar
    452 posts

    Posted 15 Aug Link to this post

    Hello Andre,

    With virtual scrolling, the height and the offset of the content are dynamic. Thus, calculating the distance is not an easy task, I even assume that it is not doable for every corner case.

    Having said that, if the desired functionality is a must, I strongly recommend switching to Endless Scrolling:
    For example:
    I hope this information is useful.


    Regards,
    Preslav
    Progress Telerik
    Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
  3. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 17 Aug in reply to Preslav Link to this post

    Hi Preslav,

    Thanks for your answer.

    I think I can deal with virtual mode as long as the grid (which is in a tabstrip) is visible. I'll post my code later : Each time I select the first/last visible record in the grid, I scroll up/down using the current row height, so the grid fetches the prev/next page if needed and it seems to work even I have to make more tests.

    Of course, I understand that I can't use same method when the tab containing the grid is hidden. So when this is the case (the visible tab is the one containing the form associated with the grid), I don't try to sync scroll position, I just select tr elements to fectch next/prev record, then when there is no more record, I explicitly fetch records by calling page() method.

    But doing this with virtual scrolling does not work. If I call page() method, even when the grid is visible, I can't scroll anymore. I just get the page but the position of scrollbar is ever at top, and no way to scroll back. Is it by design or is it a bug ?

    Please find attached png. What I did to get this : Starting from first page, when the grid is visible, I call myGrid.dataSource.page(myGrid.dataSource.totalPages());

    After that, you can see that I get indeed the last record, but the scrollbar position is wrong. I think it's easy to reproduce.

    Please note I did not try endless mode yet. But this behavior with virtual mode seems weird.

    Regards,

    André.

  4. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 17 Aug in reply to ANDRE Link to this post

    I post new png so you can see the total record number.
  5. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 17 Aug in reply to ANDRE Link to this post

    I post another PNG, so you can see the total number of records.
  6. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 17 Aug in reply to ANDRE Link to this post

    Dear Preslav,

    Here the code used to manage First/Prev/Next/Last toolbar using Virtual scrolling.

    I manage two cases : When grid is hidden or displayed.

     

     

    The function called to sync 

    scrollSync : function(){
                      
                    oTab.oGrid.resize(true);
                    var rowTop          = oTab.oGrid.select().offset().top;
                    var rowHeight       = oTab.oGrid.select().height();
                    var rowHeightNext   = rowHeight
                    var rowHeightPrev   = rowHeight
                    var rowBottom       = rowTop + rowHeight;
                    var gridTop         = oTab.oGrid.wrapper.find('.k-grid-content').offset().top;
                    var gridBottom      = gridTop + oTab.oGrid.wrapper.find('.k-grid-content').height();
                     
                    if (typeof oTab.oVars.trNext != "undefined"){
                        if (oTab.oVars.trNext != null){
                            rowHeightNext = oTab.oVars.trNext.height();
                        }
                    }
                    if (rowHeightNext==null){
                        rowHeightNext   = rowHeight;
                    }
                    if (typeof oTab.oVars.trPrev != "undefined"){
                        if (oTab.oVars.trPrev != null){
                            rowHeightPrev = oTab.oVars.trPrev.height();
                        }
                    }
                    if (rowHeightPrev==null){
                        rowHeightPrev   = rowHeight;
                    }
                     
                    if (rowBottom >= gridBottom){
                        var vs = oTab.oGrid.wrapper.find('.k-grid-content').data('kendoVirtualScrollable');                
                        var scrollTop = vs.verticalScrollbar.scrollTop();
                        var scrollTopNew = (rowBottom - gridBottom) + scrollTop + rowHeightNext;
                        vs.verticalScrollbar.animate({ scrollTop: scrollTopNew}, 250);
                    }
                    else if (rowTop <= gridTop){
                        var vs = oTab.oGrid.wrapper.find('.k-grid-content').data('kendoVirtualScrollable');                
                        var scrollTop = vs.verticalScrollbar.scrollTop();
                        var scrollTopNew = scrollTop - rowHeightPrev;
                        if (scrollTopNew >= 0){
                            vs.verticalScrollbar.animate({ scrollTop: scrollTopNew}, 250);
                        }
                    }
                }

     

    Here the code for buttons:

    first : function () {
                oTab.oVars.lScroll=false;
                if(oTab.oGrid.dataSource.page() != 1){
                    oTab.oVars.idproToSelect=null;
                    oTab.oVars.uidToSelect=null;
                    oTab.oVars.gotoFirstRow=true;
                    oTab.oGrid.dataSource.page(1);
                }
                else {
                    oTab.oGrid.select("tr:eq('0')");
                }
            },
            prev : function () {
              if (oTab.oVars.uidPrev == null){ // No previous record ?
                    // Grid hidden ? => Load records using datasource.page()
                    if (oTab.oTabs.select().index()>0){
                        if (oTab.oGrid.dataSource.page() > 1){
                            oTab.oVars.uidToSelect = "Prev"; // => Will be processed by databound
                            oTab.oVars.idproToSelect = "Prev"; // => Will be processed by databound
                            oTab.oGrid.dataSource.page(oTab.oGrid.dataSource.page() - 1);
                        }
                    }
                    return;
                }
                oTab.oVars.uidToSelect=oTab.oVars.uidPrev; // => Will be processed by databound
                oTab.oVars.idproToSelect=oTab.oVars.idproPrev; // => Will be processed by databound
                oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidPrev+"']");  
                oTab.oVars.lButton=false;
            },
            next : function () {
                if (oTab.oVars.uidNext == null){
                    // Grid hidden ? => Load via datasource.page()
                    if (oTab.oTabs.select().index()>0){
                        if (oTab.oGrid.dataSource.page() < oTab.oGrid.dataSource.totalPages()){
                            oTab.oVars.uidToSelect = "Next"; // => Will be processed by databound
                            oTab.oVars.idproToSelect = "Next"; // => Will be processed by databound
                            oTab.oGrid.dataSource.page(oTab.oGrid.dataSource.page() + 1);
                        }
                    return;            
                }
                oTab.oVars.uidToSelect=oTab.oVars.uidNext; // => Will be processed by databound
                oTab.oVars.idproToSelect=oTab.oVars.idproNext; // => Will be processed by databound
                oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidNext+"']");        
            },
            last : function () {
                if(oTab.oGrid.dataSource.page() < oTab.oGrid.dataSource.totalPages()){
                    oTab.oVars.idproToSelect=null;
                    oTab.oVars.uidToSelect=null;
                    oTab.oVars.lScroll=false;
                    // Grid hidden ?
                     if (oTab.oTabs.select().index()>0){
                        oTab.oVars.gotoPrevLastPage=true;
                        oTab.oVars.uidToSelect = "Last"; // => Will be processed by databound
                        oTab.oVars.idproToSelect = "Last"; // => Will be processed by databound
                        oTab.oGrid.dataSource.page(oTab.oGrid.dataSource.totalPages());
                    }
                    else{
                        oTab.oVars.gotoLastRow=true;
                        var s=oTab.oGrid.wrapper.find(".k-scrollbar-vertical");
                        var h=$(s).prop('scrollHeight');
                        $(s).scrollTop(h);
                    }
                }
                else {
                    // Get lastrow
                    var i=oTab.oGrid.dataSource.view().length - 1;                 
                    if (i<0){
                        i=0;
                    }
                    // Select lastrow
                    oTab.oGrid.select("tr:eq('"+i+"')");
                }
            }

     

    The change event for the grid :

    change: function (e) {
                 
                var td;
                /*
                 * Save current, prev and next record informations
                 */
                oTab.oVars.recordCurrent = this.dataItem(this.select());
                oTab.oVars.recordId=oTab.oVars.recordCurrent.id;
                 
                  
                oTab.oVars.idproCurrent = oTab.oVars.recordCurrent.idpro;
                oTab.oVars.uidCurrent = oTab.oVars.recordCurrent.uid;
                oTab.oVars.trCurrent=oTab.oGrid.element.find("tr[data-uid='" + oTab.oVars.uidCurrent +                    "']");
                oTab.oVars.trNext = oTab.oVars.trCurrent.next('tr');
                oTab.oVars.uidNext = oTab.oVars.trNext.attr("data-uid");
                td = oTab.oVars.trNext.find("td[data-field='idpro']");
                oTab.oVars.idproNext=td.html();
                 
                oTab.oVars.trPrev = oTab.oVars.trCurrent.prev('tr');
                oTab.oVars.uidPrev = oTab.oVars.trPrev.attr("data-uid");
                td = oTab.oVars.trPrev.find("td[data-field='idpro']");
                oTab.oVars.idproPrev=td.html();
     
                // Sync scrollbar
                if (oTab.oVars.lScroll){               
                    oTab.oFuns.scrollSync();
                }
                else {
                    oTab.oVars.lScroll=true;               
                }
             },

     

    This is what happens in databound, that is when new records need to be fetched  :

    dataBound: function(e) {     

                     // Last row required

                if (oTab.oVars.gotoLastRow){
                  oTab.oVars.gotoLastRow=false;
                  // Refresh scrollbar
                  oTab.oGrid.resize(true);
                  // get last row
                  var i=oTab.oGrid.dataSource.view().length - 1;  
                  // Select last row
                  oTab.oGrid.select("tr:eq('"+i+"')");
            }  

            // First row required

             else if (oTab.oVars.gotoFirstRow){
               oTab.oVars.gotoFirstRow=false;                 
               // Refresh scrollbar
               oTab.oGrid.resize(true);
              // Scroll to top
             oTab.oGrid.wrapper.find(".k-scrollbar-vertical").scrollTop(0);                    
             //  row
             oTab.oGrid.select("tr:eq('0')");
         }

         // Other row required (Next ou Prev)

         else if (oTab.oVars.idproToSelect != null){
     
                        var td;
                         // Nextwhen grid is hidden
                        if (oTab.oVars.idproToSelect == "Next"){
                             
                            // Finding first td whith field "idpro" (Progress rowid of the record)
                            td = oTab.oGrid.element.find("td[data-field='idpro']");
                             
                            // Finding the row = tr parent of td
                            oTab.oVars.trNext = td.parent();
                            oTab.oVars.idproNext=td.html();
                             
                            // Row uid
                            oTab.oVars.uidNext = oTab.oVars.trNext.attr("data-uid");
                             
                            // Grid hidden in thus case => no scroll
                            oTab.oVars.lScroll=false;
                            oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidNext+"']");
                            // re enable scroll for further oparations
                            oTab.oVars.lScroll=true;
                        }
                        // Prev when grid is hidden
                        else if (oTab.oVars.idproToSelect == "Prev"){
                             
                            // td for field idpro
                            td = oTab.oGrid.element.find("td:last[data-field='idpro']");              
                            // row = tr parent of td
                            oTab.oVars.trPrev = td.parent();
                             
                            // Row uid
                            oTab.oVars.idproPrev=td.html();
                            oTab.oVars.uidPrev = oTab.oVars.trPrev.attr("data-uid");
                             
                            // Grid hidden in this case => no scroll
                            oTab.oVars.lScroll=false;
                            oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidPrev+"']");
                            // re enable scroll
                            oTab.oVars.lScroll=true;
                        }
                        // Next or Prev when grid is displayed (scroll already occured)
                        else {
                            // uid to select using idproToSelect
                            oTab.oGrid.element.find("td[data-field='idpro']").each(function(i, item) {
                                if($(item).html() == oTab.oVars.idproToSelect) {
                                    oTab.oVars.uidToSelect=$(item).parent().attr('data-uid');
                                }
                            });
                            // Scrollalready occured so disabling it
                            oTab.oVars.lScroll=false;
                            oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidToSelect+"']");
                            oTab.oVars.lScroll=true;
                        }
                        // RAZ
                        oTab.oVars.uidToSelect=null;
                        oTab.oVars.idproToSelect=null;
                    }
                         // Row selected using mouse
                    else {
                        // Finding uid to select using idproCurrent
                        oTab.oGrid.element.find("td[data-field='idpro']").each(function(i, item) {
                            if($(item).html() == oTab.oVars.idproCurrent) {
                                oTab.oVars.uidCurrent=$(item).parent().attr('data-uid');
                            }
                        });
                        oTab.oVars.lScroll=false;
                        oTab.oGrid.select("tr[data-uid='"+oTab.oVars.uidCurrent+"']");
                        oTab.oVars.lScroll=true;
                    }
                }
             },

     

    To manage the case when grid is hidden, I try to sync grid whe it is displayed using tab event "activate"

    var onActivate = function(e) {
        // Get main object to get grid
        var oTab = window[JSTabsCurrent]["getTab"]();
        // Onglet 0 ?
        if(oTab.oTabs.select().index()==0){    
               // Just resize then call scrollSunc
            oTab.oGrid.resize(true);
            oTab.oFuns.scrollSync();   
             
        }
    }

     

    I hope it makes it clearer.

    I quicly tried the endless mode however, I can figure out how to programmatically scroll as object "kendoVirtualScrollable" does not exist in this mode. 

     

    Regards,

    André.

  7. Konstantin Dikov
    Admin
    Konstantin Dikov avatar
    2351 posts

    Posted 21 Aug Link to this post

    Hello Andre,

    Thank you for sharing your implementation with the community.

    Regarding the endless scrolling, it uses different mechanism and it will not be possible to achieve the desired behavior with it.


    Best Regards,
    Konstantin Dikov
    Progress Telerik
    Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
  8. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 21 Aug in reply to Konstantin Dikov Link to this post

    Hi,

    At this time, I update my code to make it work perfectly using paged mode. I juste replaced 

    var vs = oTab.oGrid.wrapper.find('.k-grid-content').data('kendoVirtualScrollable');

     

    by 

    var vs = oTab.oGrid.wrapper.find('.k-grid-content');

     

    It works because in paged mode, you can use page() method. 

    Endless mode is not usable because it seems to load and keep all loaded records locally. Right ? So not possible to use that with tables containing millions records.  

    However, virtual mode stays the best one for me. It's almost working. The only problem is when I use page() method to go to the last record, it loads only the last page instead of loading the last page PLUS some records from previous page as it does when you use the scrollbar. Thus, it prevents user to scrollback. That's a pity because this is the only case causing problem.

     

    In conclusion, you simply can't use page() method in virtual mode. Do you have any way to fix this or any workaround ? It would be great.   

    Regards,

    André.

  9. Konstantin Dikov
    Admin
    Konstantin Dikov avatar
    2351 posts

    Posted 23 Aug Link to this post

    Hi Andre,

    Your statement about the endless scrolling is correct and the new pages are appended to the previous pages and that functionality is not suitable for huge amount of data.

    As for the Virtual Scrolling, changing the position of the scrollbar should be enough for going to a particular page, without calling the page method of the DataSource. I have created an  example that will go to a particular page entered in the input above the Grid:
    It should be easy to replace the input element with the buttons that you have.


    Regards,
    Konstantin Dikov
    Progress Telerik
    Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
  10. ANDRE
    ANDRE avatar
    11 posts
    Member since:
    Dec 2017

    Posted 23 Aug in reply to Konstantin Dikov Link to this post

    Hello Konstantin,

    Yes, I already did something like this to go to last page when the grid is visible.

    The method you provide is working for any page.

    I have now to try it when the grid is not visible, that is only when the tab containing the grid is activated again because when the grid is hidden, I have no other choice than page() method.

    When the grid becomes visible again, I plan to first come back to first page, then resize the grid, then go to current page from within dataBound event.

    The only problem I can see at this time is when row height is not the same for all rows.  So "rowHeight = scrollbarHeight/oTab.oGrid.dataSource.total()" is not always accurate.

    I'll let you know if it suits to my case.

    Regards,

    André.

Back to Top