[CLOSED] Update page number on grid panel

  1. #1

    [CLOSED] Update page number on grid panel

    Hi
    When user clicks last page button on grid's panel paging toolbar, I am calling proxy method in code behind to go to last page of the resultset. It does it correctly and returns correct number of objects on the last page (and of course shows them), but paging toolbar control isn't updated to reflect this. After I press once again go to last page everything is fixed, since now correct StoreRequestParameters.Start parameter is sent. So I need a way to somehow update page number on pager control after user presses last page. I tried to update StoreRequestParameters in proxy, but they are read-only.

    REMARK: I don't know the number of records in advance, so I am faking it by setting new total number of records in each call of proxy to
    totalRecordCount = pagingQuery.RecordCount + limit + 1;
    That is how I distinguish if user pressed next page or last page on the pager
    if (start + limit > totalRecordCount)
    //user pressed last page
    else
    //user pressed next page
    Last edited by fabricio.murta; Jan 30, 2016 at 5:22 PM. Reason: [CLOSED]
  2. #2
    Hi,
    Do you need additional explanation, or something? Please help, I'm in a kind of hurry..
  3. #3
    Hello, and sorry for delay replying your post!

    I'm not sure how exactly you are implementing this. Think you can write a test case so we can reproduce the issue in our side and provide proper feedback?

    At first, a known total number of pages would be necessary to have the pager working. Unless you can't estimate the number of records, I believe all we can do is work-around the paging.

    In cases where the number of entries is arbitrary, maybe an infinite scrolling (with buffering not to load all the data at once -- which would make the page very slow) should be the solution in this case. But again, maybe if you provide a test case of your scenario, we could share insights keeping the pager in place.
    Fabrício Murta
    Developer & Support Expert
  4. #4
    Well, I expected that and prepared sample while I was waiting :)
    In this sample please press go to last page button. You will see that although data actually shows last page, page number and buttons are not refreshed. Then if you press go to last page again, everything is fixed and from that moment on, everything is working as expected. I don't want that extra click :)
    If this sample looks weird to you this is because I tried to strip down meaningful queries, so you can just concentrate on problem. This is a sample when you have a data source where you don't know in advance number of records you will fetch.
    Ok here's the code:
    Model:
    public class DataModel2
        {
            private static List<Dictionary<string, string>> _theData;
            private static string[] _columns;
    
            static DataModel2()
            {
                Random _rnd = new Random();
    
                int columnNum = _rnd.Next(5, 10);
                _columns = new string[columnNum];
                for (int i = 0; i < columnNum; i++)
                    _columns[i] = "Column" + i;
    
                _theData = new List<Dictionary<string, string>>();
                int dataNum = _rnd.Next(100, 500);
                for (int i = 0; i < dataNum; i++)
                {
                    Dictionary<string, string> row = new Dictionary<string, string>();
                    for (int j = 0; j < _columns.Length; j++)
                        row.Add(_columns[j], "SomeData" + i + j);
                    _theData.Add(row);
                }
            }
    
            public DataModel2(int Start, int Limit)
            {
                StartingIndex = Start;
                PageSize = Limit;
            }
    
            public static List<Dictionary<string, string>> GetData()
            {
                return _theData;
            }
    
            public static string[] GetColumns()
            {
                return _columns;
            }
    
    
            public int StartingIndex { get; set; }
    
            /// <summary>
            /// Number of records to fetch
            /// </summary>
            public int PageSize { get; set; }
    
            /// <summary>
            /// Returns true if there are more results for this query. Valid only after GetPage method is invoked
            /// </summary>
            public bool HasMoreResults { get; private set; }
    
            /// <summary>
            /// Returns number of records fetched. Valid only after GetPage method is invoked
            /// </summary>
            public int RecordCount { get; private set; }
    
            /// <summary>
            /// This method returns page of data from StartingIndex with size at most of PageSize
            /// </summary>
            /// <returns>Row properties</returns>
            public List<Dictionary<string, string>> GetPage()
            {
                int limit = PageSize;
                if (StartingIndex + PageSize > _theData.Count)
                {
                    limit = _theData.Count - StartingIndex;
                    HasMoreResults = false;
                }
                else
                    HasMoreResults = true;
    
    
                RecordCount = limit;
                List<Dictionary<string, string>> rangedData = _theData.GetRange(StartingIndex, limit);
    
                return rangedData;
            }
    
            /// <summary>
            /// This method returns last page of data taking in count PageSize
            /// </summary>
            /// <returns>Row properties</returns>
            public List<Dictionary<string, string>> LastPage()
            {
                RecordCount = _theData.Count;
                int countOfRecordsOnLastPage = RecordCount % PageSize;
                StartingIndex = RecordCount - countOfRecordsOnLastPage;
    
                List<Dictionary<string, string>> rangedData = _theData.GetRange(StartingIndex, countOfRecordsOnLastPage);
                return rangedData;
            }
        }
    View:
    @model My.Models.DataModel2
    
    @{
        var X = Html.X();
    }
    
    <script>
        function getObjects(grid) {
            grid.getStore().reload({
                params: {
                    start: 0,
                    limit: 20,
                    filter: 'all'
                }
            });
        }
    </script>
    
    
    @(
        X.GridPanel()
        .ColumnModel(cm =>
        {
            for (int i = 0; i < My.Models.DataModel2.GetColumns().Length; i++)
                cm.Columns.Add(X.Column().Text(My.Models.DataModel2.GetColumns()[i]).DataIndex(My.Models.DataModel2.GetColumns()[i]));
        })
        .Store
        (
            X.Store().AutoLoad(false).Proxy(X.AjaxProxy().Url(Url.Action("GetObjects2")).Reader(X.JsonReader().Root("data")))
            .PageSize(20)
            .Model
            (
                X.Model().Fields(fields =>
                {
                    for (int i = 0; i < My.Models.DataModel2.GetColumns().Length; i++)
                        fields.Add(new ModelField(My.Models.DataModel2.GetColumns()[i]));
                })
            )
        )
        .BottomBar(X.PagingToolbar().DisplayInfo(true).DisplayMsg("Displaying objects {0} - {1} of {2}"))
        .Listeners(l =>
        {
            l.AfterRender.Fn = "getObjects";
        })
    )
    Controler:
    public ActionResult GetObjects2(StoreRequestParameters parameters, string filter)
            {
                int start = parameters.Start;
                int limit = parameters.Limit;
                My.Models.DataModel2 pagingQuery = null;
                List<Dictionary<string, string>> objects = null;
                int totalRecordCount = 0;
                if (String.IsNullOrEmpty(filter))
                {
                    totalRecordCount = (int)TempData["recordCount"];
    
                    pagingQuery = new My.Models.DataModel2(start, limit);
    
                    if (start + limit > totalRecordCount)
                    {
                        objects = pagingQuery.LastPage();
                        totalRecordCount = pagingQuery.RecordCount;
                    }
                    else
                    {
                        objects = pagingQuery.GetPage();
                        if (totalRecordCount <= start + pagingQuery.RecordCount + 1)
                            totalRecordCount += pagingQuery.RecordCount;
                        if (!pagingQuery.HasMoreResults)
                            totalRecordCount += pagingQuery.RecordCount - 1 - limit;
                    }
    
                    TempData["recordCount"] = totalRecordCount;
                }
                else
                {
                    pagingQuery = new My.Models.DataModel2(start, limit);
                    objects = pagingQuery.GetPage();
                    if (pagingQuery.HasMoreResults)
                        totalRecordCount = pagingQuery.RecordCount + limit + 1;
                    else
                        totalRecordCount = pagingQuery.RecordCount;
    
                    TempData["recordCount"] = totalRecordCount;
                }
    
                return this.Store(new Paging<Dictionary<string, string>>(objects, totalRecordCount));
            }
  5. #5
    Hello, @ingbabic!

    Sorry for the delay, that was a tricky one!..

    Actually, as I said, the paging toolbar is not really designed to be changing like your sample, so it is not supposed to have this behavior by default.

    But here is what you can do with it in order to make it behave to your example (thanks for that test case, even with it, I needed a good crack to get a solution... wonder if there was no test case...).

    Add this JavaScript to your page:
        var storeLoadHandler = function (store, records, successful) {
            if (!store.orgTotalCount) {
                store.orgTotalCount = store.totalCount;
            } else {
                if (store.totalCount != store.orgTotalCount && store.lastPageRequested) {
                    store.orgTotalCount = store.totalCount;
                    
                    // Get +1 page if the count does not exactly divides to the amount per page
                    var lastPage = Math.ceil(store.totalCount / store.pageSize);
    
                    // Force switching to last page, enforcing the 'last page' command.
                    if (store.currentPage != lastPage) {
                        store.loadPage(lastPage);
                    }
                }
            }
    
            // Ensure lastPageRequested is set to false after every load.
            store.lastPageRequested = false;
        }
    
        Ext.define('Ext.toolbar.Paging', {
            override: 'Ext.toolbar.Paging',
            moveLast: function () {
                // Just indicate that what we want is to go to last page.
                this.store.lastPageRequested = true;
                
                return this.callParent(arguments);
            }
        });
    Then bind the 'storeLoadHandler' function to the 'Load' event handler of the grid's store. Something like:
    .Listeners(l =>
    {
        l.Load.Fn = "storeLoadHandler";
    })
    In your store block.

    I hope this does your intended action. I am afraid this would mean another proxy fetch from the client... but it can't be helped. After all, it requested page '3' thinking it was the last one, and then found that at that time, the last one became another. This pretty much adds a little 'intelligence' to the store, discerning that when the user wants to go to the last page, it should give it another try to last page.

    As it sets lastPageRequested it will attempt to move forward just once. If page count keeps growing as it tries to adapt, it will just give up after first attempt. You may implement a retry count (instead of a boolean use retry count and decrement it (instead of forcing 'false') every time the load handler script is run.

    Well, hope this helps!
    Fabrício Murta
    Developer & Support Expert
  6. #6
    Wow, that works quite cool :)
    Now only problem is if I type page number which is bigger then pager thinks it has max. For example I type 5 in text box at the beginning. It correctly goes to the last page, but the number showed is 3 (which he thought he has) and hence pager buttons are not updated (quite similar as I had before this solution, when click direct on last page button).
    Here I see 3 possible solutions:
    1) Disable typing in page number box.
    2) I type 5, it goes to last page shows real last page number and pager buttons are updated.
    3) Would be even nicer if I could somehow know that user typed page number. In that case It would be better to use GetPage method (not LastPage()). We will move to requested page and if that page is really beyond resultset count to go to last page (scenario 2)

    For me all three solutions are ok. Please help me to go through these last bits :)
  7. #7
    Okay, I've investigated the option would be best for you, but your code behind logic itself limits it. I am not sure I should fiddle with your data source simulation -- I might diverge from your actual use case.

    That said, I will give you directions on how to allow the page to send the actual page the user asked for and how to check back (once the result is received) which page you actually received (to update in the view):

    The override will then have to get a new treat for when hitting the "enter" key. This is just the start. After this is working, you'd have to also adjust page up and page down keys:
        Ext.define('Ext.toolbar.Paging', {
            override: 'Ext.toolbar.Paging',
            moveLast: function () {
                this.store.lastPageRequested = true;
                
                return this.callParent(arguments);
            },
            onPagingKeyDown: function (field, e) {
                var me = this,
                    k = e.getKey(),
                    pageData = me.getPageData(),
                    pageNum;
    
                if (k == e.RETURN) {
                    e.stopEvent();
                    pageNum = me.readPageFromInput(pageData);
                    if (pageNum !== false) {
                        this.store.attemptBeyondLastPage = pageNum > pageData.pageCount;
    
                        if (me.fireEvent('beforechange', me, pageNum) !== false) {
                            me.store.loadPage(pageNum);
                        }
                    }
                } else {
                    // if not our custom-treated key, then call original method to handle other keys
                    me.callParent(arguments);
                }
            }
        });
    The other keys that switch pages would be treated similarly, just get the original code for this method and then adapt it.

    This is the script in the "before request" part.

    Now for the "during request" part, I've added an additional row to the result (which is not displayed in the grid at all). Here are the changes I made on your getObjects2 method:

     var pageReturned = parameters.Page;
    (...)
    if (start + limit > totalRecordCount) { // not changed -- just for reference
        (...)
        pageReturned = (totalRecordCount - (totalRecordCount % parameters.Limit)) / parameters.Limit;
    }
    (...)      
    var lastPageSpecialRow = new Dictionary<string, string>();
    lastPageSpecialRow.Add("returnedPage",pageReturned); // get the actual last page, instead of a fixed '2'
    objects.Add(lastPageSpecialRow);
    
    return this.Store(new Paging<Dictionary<string, string>>(objects, totalRecordCount)); // not changed -- just for reference
    And then, upon store reload (the "after part of the history"), we have to chew this new data. So we change the store handler function like this:
        var storeLoadHandler = function (store, records, successful) {
            if (!store.orgTotalCount) {
                store.orgTotalCount = store.totalCount;
            } else {
                var skBeyondLast = store.lastPageRequested || store.attemptBeyondLastPage;
                if (store.totalCount != store.orgTotalCount && skBeyondLast) {
                    store.orgTotalCount = store.totalCount;
                    
                    if (store.lastPageRequested) {
                        // Get +1 page if the count does not exactly divides to the amount per page
                        var lastPage = Math.ceil(store.totalCount / store.pageSize);
    
                        // Force switching to last page, enforcing the 'last page' command.
                        if (store.currentPage != lastPage) {
                            store.loadPage(lastPage);
                        }
                    } else { // it can only be attemptBeyondLastPage
                        var storeItems = store.data.items,
                            lastItem = storeItems[storeItems.length - 1],
                            returnedPage = parseInt(lastItem.raw.returnedPage);
    
                        // If we really got the returnedPage value from the request
                        if (!isNaN(returnedPage)) {
                            // And the page is not really the same page we requested
                            if (returnedPage != store.currentPage) {
                                // This is just being pragmatic, far from optimal:
                                store.loadPage(returnedPage);
    
                                // It would rather start from here:
                                // Update the value of current page in the field
                                //App.pt1.getInputItem().setValue(returnedPage);
                                // Then update the value of 'Displaying objects .. - .. of ..
                            }
                        }
                    }
                }
            }
    At this point, the code is working like your option '2'. If anything beyond the currently known last page is asked, it gets the last page. But the limitation is actually on the code behind part. It is now up to you whether you can or cannot handle your option 3.

    The problem is that your code behind logic also does not know how many pages it actually has before calling getLastPage.

    This part of the code gets triggered and then it always gets the last page if I choose anything beyond the current known last page and before actual (random) last page). For example, if I choose page '4'.
                    totalRecordCount = (int)TempData["recordCount"];
                    pagingQuery = new DataModel2(start, limit);
    
                    if (start + limit > totalRecordCount)
                    {
                        objects = pagingQuery.LastPage();
                        totalRecordCount = pagingQuery.RecordCount;
                        pageReturned = (totalRecordCount - (totalRecordCount % parameters.Limit)) / parameters.Limit;
                    }
    I'm not sure if your actual scenario allows you to evaluate the actual totalRecordCount before making the test. I may have changed your controller code to this extent but I would just be giving long shots that may not be useful.

    So, again, it is up to you whether you can or cannot get the total amount or records before that test and accurately fetch the requested page. If you can stick to option 2 or deepen your work to have it actually jump to an existing page if possible.

    One possibility I'd think about was if DataModel2 had means to get the updated totalRecordCount up front. This might though, trouble that behavior (that I didn't pay too much attention to so far) you have when you move one page by a time (that keeps stacking up total amount of data, increasing last page every 'next page' you click).

    Well, I hope the text is not very boring to read :P.. and also hope it helps!
    Fabrício Murta
    Developer & Support Expert
  8. #8
    Thank you very much, you did hell of a job :)
    I adopted your solution, for now, for scenario 2. You're right, for scenario 3 some more work have to be done, for server and controller side, for what currently I don't have a time (we're in a quite rush to achieve everything). So for fellow lazy programmers (like me), if someone crash there, just change 'Ext.toolbar.Paging' override like this:

           Ext.define('Ext.toolbar.Paging', {
            override: 'Ext.toolbar.Paging',
            moveLast: function () {
                this.store.lastPageRequested = true;
                return this.callParent(arguments);
            },
            onPagingKeyDown: function (field, e) {
    
                var k = e.getKey(),
                pageData = this.getPageData(),
                pageNum;
                if (k == e.RETURN) {
                    e.stopEvent();
                    pageNum = this.readPageFromInput(pageData);
                    if (pageNum !== false) {
                        this.store.lastPageRequested = pageNum >= pageData.pageCount;
                        return this.callParent(arguments);
                    }
                } else {
                    this.callParent(arguments);
                }
            }
        });
  9. #9
    Glad it was of some help! I guess it is okay to mark this thread with [CLOSED] head tag now? If there's something else, just let us know!
    Fabrício Murta
    Developer & Support Expert

Similar Threads

  1. Update page number in Paging toolbar
    By ingbabic in forum 2.x Help
    Replies: 0
    Last Post: Sep 04, 2014, 4:14 PM
  2. [CLOSED] Navigate to a page on a grid using the page number
    By optibase in forum 1.x Legacy Premium Help
    Replies: 1
    Last Post: Aug 06, 2012, 5:47 PM
  3. Replies: 0
    Last Post: Dec 02, 2010, 9:01 PM
  4. Replies: 14
    Last Post: Nov 02, 2010, 7:44 AM
  5. [CLOSED] Grid Page Number
    By vali1993 in forum 1.x Legacy Premium Help
    Replies: 2
    Last Post: Oct 14, 2010, 12:46 PM

Tags for this Thread

Posting Permissions