Page 1 of 2 12 LastLast
Results 1 to 10 of 16

Thread: [4.1.0RC2] Remote filter with buffered grid bug

    Wait! Looks like we don't have enough information to add this to bug database. Please follow this template bug format.
  1. #1

    Default [4.1.0RC2] Remote filter with buffered grid bug

    REQUIRED INFORMATION


    Ext version tested:
    • Ext 4.1.0RC2
    Browser versions tested against:
    • Chrome
    DOCTYPE tested against:
    • HTML
    Description:
    • Remote filter with buffered grid bug, see further info in workaround
    Steps to reproduce the problem:
    • Filter the store so that the result set is smaller than the page size
    • Change the filter
    The result that was expected:
    • The new result set
    The result that occurs instead:
    • Nothing
    • one of the things that goes wrong is the if count == totalcount then return in prefetchPage
    Test Case:

    Code:
         Had no time yet


    HELPFUL INFORMATION

    Debugging already done:
    • See fix
    Possible fix:
    • PHP Code:
      /*
       * BUG BUFFERED GRID REMOTE FILTER
       *
       * The grid doesn't get get updated count(bufrecs) == count(totalrecs)
       * The grid doesn't update the full view.
       * Also it asumes that the new set will be smaller, a wrong asumption.
       *
       * http://www.sencha.com/forum/showthread.php?193340
       */
      Ext.syncRequire('Ext.data.Store');
      Ext.override(Ext.data.Store, {
          
      filter: function(filtersvalue) {
              if (
      Ext.isString(filters)) {
                  
      filters = {
                      
      propertyfilters,
                      
      valuevalue
                  
      };
              }

              var 
      me this,
                  
      decoded me.decodeFilters(filters),
                  
      0count,
                  
      doLocalSort me.sorters.length && me.sortOnFilter && !me.remoteSort,
                  
      length decoded.length;

              for (; 
      lengthi++) {
                  
      me.filters.replace(decoded[i]);
              }

              if (
      me.remoteFilter) {
                  
      // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
                  // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
                  // via the guaranteedrange event
                  
      if (me.buffered) {
                      
      count me.getCount();
                      
      me.pageMap.clear();
      // [
      // The're is no code to do what is stated in the comments:
      // "reload the visible part of the Store via the guaranteedrange event"
      // So use loadPage instead, that causes:
      // loadPage -> loadToPrefetch -> prefetch -> onItitialPrefetch -> guaranteeRange (full view)
      //
      // --
      //                me.prefetchPage(1, {
      //                    callback: function(records, operation, success) {
      //                        if (success) {
      //                            me.onGuaranteedRange({
      //                                prefetchStart: 0,
      //                                prefetchEnd: Math.min(count, records.length)
      //                            });
      //                        }
      //                    }
      //                });
      // ++
                      
      me.loadPage(1);
      // ]
                  
      } else {
                      
      // Reset to the first page, the filter is likely to produce a smaller data set
                      
      me.currentPage 1;
                      
      //the load function will pick up the new filters and request the filtered data from the proxy
                      
      me.load();
                  }
              } else {
                  
      /**
                   * @property {Ext.util.MixedCollection} snapshot
                   * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
                   * records when a filter is removed or changed
                   */
                  
      if (me.filters.getCount()) {
                      
      me.snapshot me.snapshot || me.data.clone();
                      
      me.data me.data.filter(me.filters.items);

                      if (
      doLocalSort) {
                          
      me.sort();
                      } else {
                          
      // fire datachanged event if it hasn't already been fired by doSort
                          
      me.fireEvent('datachanged'me);
                          
      me.fireEvent('refresh'me);
                      }
                  }
              }
          }
      });
      Ext.syncRequire('Ext.grid.PagingScroller');
      Ext.override(Ext.grid.PagingScroller, {
          
      // Used for variable row heights. Try to find the offset from scrollTop of a common row
          // Ensure, upon each refresh, that the stretcher element is the correct height
          
      onViewRefresh: function() {
              var 
      me this,
                  
      store me.store,
                  
      newScrollHeight,
                  
      view me.view,
                  
      viewEl view.el,
                  
      viewDom viewEl.dom,
                  
      rows,
                  
      newScrollOffset,
                  
      scrollDelta,
                  
      table,
                  
      tableTop,
                  
      scrollTop;

      // [
      // This leaves the stretcher when the previous set was large and the new one empty,
      // so remove it.
      // --
      //        if (!store.getCount()) {
      //            return;
      //        }
      // ]

              // No scroll monitoring is needed if
              //    All data is in view OR
              //  Store is filtered locally.
              //    - scrolling a locally filtered page is obv a local operation within the context of a huge set of pages
              //      so local scrolling is appropriate.
              
      if (store.getCount() === store.getTotalCount() || (store.isFiltered() && !store.remoteFilter)) {
                  
      me.stretcher.setHeight(0);
                  return (
      me.disabled true);
              } else {
                  
      me.disabled false;
              }

              
      me.stretcher.setHeight(newScrollHeight me.getScrollHeight());

              
      scrollTop viewDom.scrollTop;

              
      // Flag to the refreshHeight interceptor that regular refreshHeight postprocessing should be vetoed.
              
      me.isScrollRefresh = (scrollTop 0);

              
      // If we have had to calculate the store position from the pure scroll bar position,
              // then we must calculate the table's vertical position from the scrollProportion
              
      if (me.scrollProportion !== undefined) {
                  
      table me.viewEl.child('table'true);
                  
      me.scrollProportion scrollTop / (newScrollHeight table.offsetHeight);
                  
      table me.viewEl.child('table'true);
                  
      table.style.position 'absolute';
                  
      table.style.top = (me.scrollProportion ? (newScrollHeight me.scrollProportion) - (table.offsetHeight me.scrollProportion) : 0) + 'px';
              }
              else {
                  
      table viewEl.child('table'true);
                  
      table.style.position 'absolute';
                  
      table.style.top = (tableTop = (me.tableStart||0) * me.rowHeight) + 'px';

                  
      // ScrollOffset to a common row was calculated in beforeViewRefresh, so we can synch table position with how it was before
                  
      if (me.scrollOffset) {
                      
      rows view.getNodes();
                      
      newScrollOffset = -viewEl.getOffsetsTo(rows[me.commonRecordIndex])[1];
                      
      scrollDelta newScrollOffset me.scrollOffset;
                      
      me.position = (scrollTop += scrollDelta);
                  }

                  
      // If the table is not fully in view view, scroll to where it is in view.
                  // This will happen when the page goes out of view unexpectedly, outside the
                  // control of the PagingScroller. For example, a refresh caused by a remote sort or filter reverting
                  // back to page 1.
                  // Note that with buffered Stores, only remote sorting is allowed, otherwise the locally
                  // sorted page will be out of order with the whole dataset.
                  
      else if ((tableTop scrollTop) || ((tableTop table.offsetHeight) < scrollTop viewDom.clientHeight)) {
                      
      me.position viewDom.scrollTop tableTop;
                  }
              }
          }
      }); 

  2. #2
    Sencha Premium User mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    40,379

    Default

    This looks to be a dup of your bug post here: http://www.sencha.com/forum/showthread.php?138374

    While I get with a core dev, can provide a test case so that we can make sure this is fixed for good?
    Mitchell Simoens @LikelyMitch

    Check out my GitHub:
    https://github.com/mitchellsimoens

    Posts are my own, not any current, past or future employer's.

  3. #3

    Default

    @mitchellsimoens though it's in the same function, it is a completely different bug from the other one I reported.

    While i didn't had the time to produce a test case animal has a screenshot of something that could be the base for a good test case here:
    http://www.sencha.com/forum/showthre...l=1#post754501

  4. #4
    Sencha User Animal's Avatar
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,890

    Default

    I am adding that example as a separate example page, in addition to the infinite-scroll example.

    It works with the latest code.

  5. #5
    Sencha User Animal's Avatar
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,890

    Default

    Here's the new example code which works with the latest code.

    The key fix you need is here: http://www.sencha.com/forum/showthre...l=1#post771690

    Looks like that commit did not make it into the RC2 build.

    When you have that override in place, add this to examples/grid as infinite-scroll-with-filter.html

    Code:
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>Infinite Scroll Grid Example with remote filtering</title>
    
        <link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css"/>
        <link rel="stylesheet" type="text/css" href="../shared/example.css" />
    
        <!-- GC -->
    
        <script type="text/javascript" src="../../ext-all.js"></script>
    
        <script type="text/javascript" src="infinite-scroll-with-filter.js"></script>
    </head>
    <body>
        <h1>Infinite Scrolling with remote filtering</h1>
        <p>Ext JS 4's brand new grid supports infinite scrolling, which enables you to load any number of records into a grid without paging.</p>
        <p>The new grid uses a virtualized scrolling system to handle potentially infinite data sets without any impact on client side performance.</p>
        <p>The code is not minified, see <a href="infinite-scroll.js">infinite-scroll.js</a></p>
        <p>As the edge of the rendered data scrolls towards being in view, the table is refreshed, and repositioned to maintain
        visual scroll position.</p>
    </body>
    </html>
    Add this to examples/grid as infinite-scroll-with-filter.js

    Code:
    Ext.Loader.setConfig({enabled: true});
    
    Ext.Loader.setPath('Ext.ux', '../ux/');
    Ext.require([
        'Ext.grid.*',
        'Ext.data.*',
        'Ext.util.*',
        'Ext.grid.PagingScroller',
        'Ext.ux.form.SearchField'
    ]);
    
    Ext.onReady(function(){
        Ext.define('ForumThread', {
            extend: 'Ext.data.Model',
            fields: [
                {
                name: 'title',
                mapping: 'topic_title'
                }, {
                name: 'forumtitle',
                mapping: 'forum_title'
                }, {
                name: 'forumid',
                type: 'int'
                }, {
                name: 'username',
                mapping: 'author'
                }, {
                    name: 'replycount', 
                    mapping: 'reply_count',
                    type: 'int'
                }, {
                    name: 'lastpost', 
                    mapping: 'post_time', 
                    type: 'date', 
                    dateFormat: 'timestamp'
                },
                'lastposter', 'excerpt', 'topic_id'
            ],
            idProperty: 'post_id'
        });
    
        // create the Data Store
        var store = Ext.create('Ext.data.Store', {
            id: 'store',
            model: 'ForumThread',
            remoteSort: true,
            // allow the grid to interact with the paging scroller by buffering
            buffered: true,
            
            // The topics-remote.php script appears to be hardcoded to use 50, and ignores this parameter, so we
            // are forced to use 50 here instead of a possibly more efficient value.
            pageSize: 50,
    
            // This web service seems slow, so keep lots of data in the pipeline ahead!
            leadingBufferZone: 500,
            proxy: {
                // load using script tags for cross domain, if the data in on the same domain as
                // this page, an HttpProxy would be better
                type: 'jsonp',
                url: 'http://www.sencha.com/forum/topics-remote.php',
                reader: {
                    root: 'topics',
                    totalProperty: 'totalCount'
                },
                // sends single sort as multi parameter
                simpleSortMode: true,
                
                // Parameter name to send filtering information in
                filterParam: 'query',
    
                // The PHP script just use query=<whatever>
                encodeFilters: function(filters) {
                    return filters[0].value;
                }
            },
            remoteFilter: true,
            sorters: [{
                property: 'lastpost',
                direction: 'DESC'
            }],
            autoLoad: true
        });
    
        function renderTopic(value, p, record) {
            return Ext.String.format(
                '<a href="http://sencha.com/forum/showthread.php?p={1}" target="_blank">{0}</a>',
                value,
                record.getId()
            );
        }
    
        var grid = Ext.create('Ext.grid.Panel', {
            width: 700,
            height: 500,
            collapsible: true,
            title: 'ExtJS.com - Browse Forums',
            store: store,
            loadMask: true,
            dockedItems: [{
                dock: 'top',
                xtype: 'toolbar',
                items: {
                    width: 400,
                    fieldLabel: 'Search',
                    labelWidth: 50,
                    xtype: 'searchfield',
                    store: store
                }
            }],
            selModel: {
                pruneRemoved: false
            },
            multiSelect: true,
            viewConfig: {
                trackOver: false
            },
            // grid columns
            columns:[{
                xtype: 'rownumberer',
                width: 50,
                sortable: false
            },{
                tdCls: 'x-grid-cell-topic',
                text: "Topic",
                dataIndex: 'title',
                flex: 1,
                renderer: renderTopic,
                sortable: false
            },{
                text: "Author",
                dataIndex: 'username',
                width: 100,
                hidden: true,
                sortable: true
            },{
                text: "Replies",
                dataIndex: 'replycount',
                align: 'center',
                width: 70,
                sortable: false
            },{
                id: 'last',
                text: "Last Post",
                dataIndex: 'lastpost',
                width: 130,
                renderer: Ext.util.Format.dateRenderer('n/j/Y g:i A'),
                sortable: true
            }],
            renderTo: Ext.getBody()
        });
    });

  6. #6

    Default

    Sorry but that fix ins't good enough, there are still serveral problems:

    If the current set is empty (getCount == 0 in prefetchPage()) code is halted.
    (also note that end in prefetchPage is never used)

    So instead of using prefetchPage you could better use:
    Code:
    me.prefetch({
        page     : 1,
        start    : 0,
        limit    : me.pageSize || me.defaultPageSize,
        callback: function(records, operation, success) {
            if (success) {
                me.onGuaranteedRange({
                    prefetchStart: 0,
                    prefetchEnd: Math.min(count, records.length)
                });
            }
        }
    });
    But then it only loads the first N records of the prefetched 1st page, where N is getCount+1. So if the new set is bigger than the current set+1 than these records are not displayed. Also, only the first page is prefetched, if the view size is bigger than the pagesize, the view is not filled.

    My fix fixes all these issues by using loadPage instead, that causes:loadPage -> loadToPrefetch -> prefetch -> onItitialPrefetch -> guaranteeRange (full view)

    Also there's a small fix to Ext.grid.PagingScroller so that the stretcher is removed if there are no records in the current set.

  7. #7
    Sencha User Animal's Avatar
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,890

    Default

    viewSize bigger than pageSize?

  8. #8

    Default

    By that I mean the number of records that a grid can show without scrolling.

  9. #9
    Sencha User Animal's Avatar
    Join Date
    Mar 2007
    Location
    Bédoin/Nottingham
    Posts
    30,890

    Default

    It's just not that simple.

    What I have now for the filter method is

    Code:
        filter: function(filters, value) {
            if (Ext.isString(filters)) {
                filters = {
                    property: filters,
                    value: value
                };
            }
    
            var me = this,
                decoded = me.decodeFilters(filters),
                i = 0,
                doLocalSort = me.sorters.length && me.sortOnFilter && !me.remoteSort,
                length = decoded.length;
    
            for (; i < length; i++) {
                me.filters.replace(decoded[i]);
            }
    
            if (me.remoteFilter) {
                // For a buffered Store, we have to clear the prefetch cache because the dataset will change upon filtering.
                // Then we must prefetch the new page 1, and when that arrives, reload the visible part of the Store
                // via the guaranteedrange event
                if (me.buffered) {
                    me.pageMap.clear();
                    
                    // Sanity check must not limit requested range
                    me.totalCount = Number.MAX_VALUE;
                    me.prefetchPage(1, {
                        callback: function(records, operation, success) {
                            if (success) {
                                me.onGuaranteedRange({
                                    prefetchStart: 0,
                                    prefetchEnd: Math.min(me.viewSize, records.length)
                                });
                            }
                        }
                    });
    
                    // Then prefetch any further pages needed to satisfy the buffer zone
                    me.prefetchRange(0, me.leadingBufferZone);
                } else {
                    // Reset to the first page, the filter is likely to produce a smaller data set
                    me.currentPage = 1;
                    //the load function will pick up the new filters and request the filtered data from the proxy
                    me.load();
                }
            } else {
                /**
                 * @property {Ext.util.MixedCollection} snapshot
                 * A pristine (unfiltered) collection of the records in this store. This is used to reinstate
                 * records when a filter is removed or changed
                 */
                if (me.filters.getCount()) {
                    me.snapshot = me.snapshot || me.data.clone();
                    me.data = me.data.filter(me.filters.items);
    
                    if (doLocalSort) {
                        me.sort();
                    } else {
                        // fire datachanged event if it hasn't already been fired by doSort
                        me.fireEvent('datachanged', me);
                        me.fireEvent('refresh', me);
                    }
                }
            }
        },
    Similar handling for clearFilter and doSort

  10. #10

    Default

    No it's not simple at all! But the new fix looks better. Could you explain me what happens in case of pageSize < viewSize with the new code?

    And why not use prefetch() directly so that this is not needed:
    // Sanity check must not limit requested range
    me.totalCount = Number.MAX_VALUE;

Page 1 of 2 12 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •