1. #51
    Sencha User
    Join Date
    Jan 2011
    Posts
    26
    Vote Rating
    1
    danmux is on a distinguished road

      0  

    Default


    Looking like this will be awesome, thanks - though it is only showing the first 50 when I dynamically bind my data store after construction.

    Any pointers as to why this might be before I try and debug?

  2. #52
    Sencha User
    Join Date
    Jan 2011
    Posts
    26
    Vote Rating
    1
    danmux is on a distinguished road

      0  

    Default


    I found that this.height returned undefined - i'm not sure if this is because the panel is autoHeight or because the store is bound after construction.

    but I've sent a git pull request of my changes.

  3. #53
    Sencha User
    Join Date
    Apr 2011
    Posts
    7
    Vote Rating
    0
    Ghostface is on a distinguished road

      0  

    Default


    Quote Originally Posted by danmux View Post
    Looking like this will be awesome, thanks - though it is only showing the first 50 when I dynamically bind my data store after construction.

    Any pointers as to why this might be before I try and debug?
    I have a similiar issue, I dynamically load content into my store before the list is shown (but already instantiated) and rows stop rendering after 50 entries ( I can still scroll but it's just a white area then).

  4. #54
    Sencha User
    Join Date
    Jan 2011
    Posts
    26
    Vote Rating
    1
    danmux is on a distinguished road

      0  

    Default


    This is the change I made that fixes that issue....

    https://github.com/danmux/UxBufList-...f8c7cb12bf9ad6

    line 179 ish...

    <pre>
    Ext.ux.BufferedList = Ext.extend(Ext.List, {
    var elems = this.all.elements,
    nElems = elems.length,
    returnArray = [],
    - thisHeight = this.height,
    + thisHeight = this.getHeight(),
    node,
    </pre>

  5. #55
    Sencha User
    Join Date
    Apr 2011
    Posts
    7
    Vote Rating
    0
    Ghostface is on a distinguished road

      0  

    Default


    Awesome the latest version works!

    One more thing, there appears to be a problem when using groupheaders with entries that start with a number.

    For whatever reason, as a workaround always calling

    this.groupIndexMap = recmap; at the end of initGroupIndexMap seems to solve the problem. A proper fix might be better tho.

  6. #56
    Touch Premium Member hotdp's Avatar
    Join Date
    Nov 2010
    Location
    Denmark
    Posts
    603
    Vote Rating
    14
    hotdp will become famous soon enough

      0  

    Default


    Hello
    I posted in the old thread so I will also post my problem here.

    I have problems with the performance using this list:
    Code:
    var baseConfig = {
        itemTpl: new Ext.XTemplate(
            '<tpl for=".">',
            '<div class="recipeList">',
            '<div class="recipeListImage"><img src="{image}" /></div>',
            '<div class="recipeContent">',
            '<span class="recipeName">{name}</span>',
            '<span class="recipeText">{teaser}</span>',
            '<div class="recipeTime">',
            '<em>Tid:</em>',
            '<i>{time}</i>',
            '<em>&nbsp;|&nbsp;</em>',
            '<span class="oneStar"></span>',
            '</div>',
            '</div>',
            '</div>',
            '</tpl>',
            {
                compiled: true
            }
        ),
        maxItemHeight: 130,
        blockScrollSelect: true,
        batchSize: 15,
        minimumItems: 15,
        store: store //(100 items)
    };
    
    
    var list = new Ext.ux.BufferedList(Ext.apply(baseConfig, {
        width: 500,
        height: 700
    }));
    When I scroll fast it lags and "flickers" (I think its when loading new components)
    I have tried to increment batchSize and minimumItems to 25 but with same result.
    Is it because i have images and other stuff on the items since it performance so bad or what am i doing wrong?

  7. #57
    Touch Premium Member
    Join Date
    Jul 2011
    Posts
    10
    Vote Rating
    0
    jlscott3 is on a distinguished road

      0  

    Default iOS render issue with 100s of items in list?

    iOS render issue with 100s of items in list?


    Hi-

    We're happy we found UxBufferedList, but we're having a strange issue on iOS when we have a list with a couple hundred items or more in it. Here's how the list is declared:

    Code:
    var tpl = ['templates', 'omitted', 'for', 'brevity'];
    
        	this.list = new Ext.ux.BufferedList({
        		itemTpl: tpl.join(''),
        		itemCls: 'mdp-list-item',
        		store: this.store,
        		grouped: false,
        		cls: 'schedule-list',
        		maxItemHeight: 98,
        		blockScrollSelect: true,
        		blockSize: 10,
        		emptyText: 'No visits found'
        		
        	});
    In these long-list situations, we will sometimes see a blank rectangle at the start of the list. It's impossible to screenshot; I had to take a picture of it with my webcam:

    2011-07-14_0003.jpg

    It disappears as soon as we touch the screen, or it usually disappears of its own accord after a few seconds.

    Anybody else seen anything like this? We've tried playing with the block size and template, but that didn't seem to have an effect.

  8. #58
    Sencha User Surykat's Avatar
    Join Date
    Jul 2011
    Location
    BIALYSTOK, Poland
    Posts
    58
    Vote Rating
    1
    Surykat is on a distinguished road

      0  

    Default UxBufferedList + searchfield

    UxBufferedList + searchfield


    At start, I want to thank You for great and incredible fast list component - it was that I was looking for!

    But I have strange problem with combine a searchfield with your list. When I focus (click/tap) at textbox in my toolbar i see two textboxes at the same time when the keyboard appears... when I hide keyboard everythings back to normal.

    The code is:
    Code:
    pages.ListSearch = new Ext.Panel({
        title : 'List+search',
        layout : 'fit',
        fullscreen: true,
        scroll : 'vertical',
        dockedItems : [ {
            xtype : 'toolbar',
            dock : 'top',
    
            items : [ {
                xtype : 'searchfield',
                width : '95%',
                placeHolder : 'Search...',
                listeners : {
                    scope : this,
    
                    keyup : function(field) {
                        var value = field.getValue();
    
                        if (!value) {
                            store.filterBy(function() {
                                return true;
                            });
                        } else {
                            var searches = value.split(' '), regexps = [], i;
    
                            for (i = 0; i < searches.length; i++) {
                                if (!searches[i])
                                    return;
                                regexps.push(new RegExp(searches[i], 'i'));
                            };
    
                            store.filterBy(function(record) {
                                var matched = [];
    
                                for (i = 0; i < regexps.length; i++) {
                                    var search = regexps[i];
    
                                    if (record.get('nazwa').match(search))
                                        matched.push(true);
                                    else
                                        matched.push(false);
                                }
    
                                if (regexps.length > 1
                                        && matched.indexOf(false) != -1) {
                                    return false;
                                } else {
                                    return matched[0];
                                }
                            });
                        }
                    }
                }
            } ]
        } ],
        items : [ {
            layout: 'fit',
            title : 'uxBufList',
            xtype : 'bufflist',
            itemTpl : '{name}',
            store : store
        } ]
    });
    The buflist is working fine, searching also but this behaviour with showing two textboxes one above the other is annoying.

    Mayby You could aim me which method could cousing that behavior? Thanks for help and I'm waiting for your reply.

  9. #59
    Sencha User
    Join Date
    Jul 2011
    Posts
    15
    Vote Rating
    0
    Nikkelmann is on a distinguished road

      0  

    Question Render/Scroll problem w/o height property

    Render/Scroll problem w/o height property


    Hi!

    Fantastic job on taming the poor List component!

    I have a few issues with the buffered list though.

    If I place it inside a panel with the following config:
    Code:
    {
                autoRender: true,
                floating: true,
                modal: true,
                centered: true,
                hideOnMaskTap: false,
                fullscreen: Ext.is.Phone,
                height: 400,
                layout: 'fit',
                items: [
                    {
                        xtype: 'bufferedlist',
                        store: me.store,
                        itemTpl: me.itemTpl
                        scroll: 'vertical',
                        singleSelect: false,
                        onItemDisclosure: function (record, btn, index) {
                            me.fireEvent('select', record);
                            me.close();
                        }
                    }
                ]
     }
    The list will render the first 50 items (default) but when I scroll down it will not render any more.
    If I set the height property of the buffered list, it will work, but this is not optimal for an app that has to render to many different screens and layouts.

    Another thing is that if I scroll down to the +50 item range, say 60-110 and card flip to another panel then back again, the scroll is persisted, but the items are not rendered. If I then scroll the items are rendered immediately.


    "singleSelect: false" also does not work or is it just me?
    Edit:
    "disableSelection: true" does what "singleSelect: false" should do?
    Docs:
    singleSelect : Boolean
    True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
    Edit2: Doh...
    I just understood what "singleSelect: false" is supposed to REALLY do.
    "false to allow no selection at all" aka "a list that has no item selected".

    Edit3:
    Fixed a bug in onScrollStop:
    Code:
        onScrollStop: function () {
    
            // prevents the list from selecting an item if the user just taps to stop the scroll
            if (this.blockScrollSelect && !this.disableSelection) {
                this.selModel.setLocked(true);
                Ext.defer(this.unblockSelect, 100, this);
            }
    ...
        },
    Without this, the list would become selectable again.

    Here is the complete UxBufList.js that I have compiled using most of the code snippets of the post in this thread. It fixes almost all of the above problems.
    Code:
    /*
     * 
     * Author: Scott Borduin, Lioarlan, LLC
     * License: GPL (http://www.gnu.org/licenses/gpl.html) -or- MIT (http://www.opensource.org/licenses/mit-license.php)
     * 
     * Release: 0.15
     * 
     * Modified by: Nikkelmann
     * 27-07-2011
     * - Bugfixes based on the feedback in this thread: http://www.sencha.com/forum/showthread.php?121225-High-Performance-Large-List-component-UxBufferedList
     *
     * Acknowledgement: Based partly on public contributions from members of the Sencha.com bulletin board.
     * 
     */
    
    Ext.namespace('Ext.ux');
    
    Ext.ux.BufferedList = Ext.extend(Ext.List, {
    
        // minimum number of items to be rendered at all times.
        minimumItems: 50,
    
        // number of items to render incrementally when scrolling past
        // top or bottom of currently rendered items.
        batchSize: 50,
    
        // maximum number of items to be rendered before cleanup is
        // triggered on scrollStop. Must be > batchSize.
        cleanupBoundary: 125,
    
        // if this is true, block item selection while the list is still scrolling
        blockScrollSelect: false,
    
        // this is a reasonable default, but still better to define it in config parameters
        maxItemHeight: 85,
    
        itemCls: '',
    
        // override
        initComponent: function () {
    
            this.rebuildTemplate();
    
            Ext.ux.BufferedList.superclass.initComponent.call(this);
    
            // new template which will only be used for our proxies
            this.tpl = new Ext.XTemplate([
                '<tpl for=".">',
                    '<div class="{id}"></div>',
                '</tpl>'
            ]);
    
            // Member variables to hold indicies of first and last items rendered.
            this.topItemRendered = 0;
            this.bottomItemRendered = 0;
    
            // cleanup task to be invoked on scroll stop.
            this.cleanupTask = new Ext.util.DelayedTask(this.itemCleanup, this);
    
            // flag used to make sure we don't collide with the cleanup thread
            this.isUpdating = false;
    
            // variables used to store state for group header display
            this.headerText = '';
            this.groupHeaders = [];
    
            // make sure grouping flags consistently initialized
            if (this.useGroupHeaders === undefined) {
                this.useGroupHeaders = this.grouped;
            }
            else {
                this.grouped = this.grouped || this.useGroupHeaders;
            }
    
        },
    
        rebuildTemplate: function () {
    
            // If the template is defined as an array or an XTemplate it will have to be converted into a string
            var memberFnsCombo = {};
            if (Ext.isArray(this.itemTpl)) {
                this.itemTpl = this.itemTpl.join('');
            } else if (this.itemTpl && this.itemTpl.html) {
                Ext.apply(memberFnsCombo, this.itemTpl.initialConfig);
                this.itemTpl = this.itemTpl.html;
            }
    
            this.itemTplDelayed = '<tpl for="."><div class="x-list-item ' + this.itemCls + '"><div class="x-list-item-body">' + this.itemTpl + '</div>';
    
            // onItemDisclosure support
            if (this.onItemDisclosure) {
                this.itemTplDelayed += '<div class="x-list-disclosure"></div>';
            }
            // end onItemDisclosure support
    
            this.itemTplDelayed += '</div></tpl>';
            this.itemTplDelayed = new Ext.XTemplate(this.itemTplDelayed, memberFnsCombo).compile();
        },
    
        resetScroll: function () {
            this.scroller.scrollTo({ x: 0, y: 0 });
        },
    
        // don't handle all records, but only return three: top proxy, container, bottom proxy
        // actual content will be rendered to the container element in the scroll event handler
        collectData: function (records, startIndex) {
            return [{
                id: 'ux-list-top-proxy'
            }, {
                id: 'ux-list-container'
            }, {
                id: 'ux-list-bottom-proxy'
            }];
        },
    
        // @private - override so we can remove base class scroll event handlers
        initEvents: function () {
            Ext.ux.BufferedList.superclass.initEvents.call(this);
            // Remove listeners added by base class, these are all overridden
            // in this implementation.
            this.mun(this.scroller, {
                scrollstart: this.onScrollStart,
                scroll: this.onScroll,
                scope: this
            });
    
            // monitor for hide events, to stop scrolling when hide is called
            this.mon(this,
                { beforehide: this.onBeforeHide }
            );
    
        },
    
        // @private - override of refresh from DataView.
        refresh: function () {
    
            // DataView.refresh renders our proxies and list container
            Ext.ux.BufferedList.superclass.refresh.apply(this, arguments);
    
            // if the store is unbound then the el appears to be null - once bound this is re-called and the el not null
            if (this.getTargetEl()) {
    
                // locate our proxy and list container nodes
                this.topProxy = this.getTargetEl().down('.ux-list-top-proxy');
    
                this.bottomProxy = this.getTargetEl().down('.ux-list-bottom-proxy');
    
                this.listContainer = this.getTargetEl().down('.ux-list-container');
    
                // if our store is not yet filled out, do nothing more
                if (this.store.getCount() === 0) {
                    return;
                }
    
                // if this is a grouped list, initialize group index map
                if (this.grouped) {
                    this.initGroupIndexMap();
                    this.groupHeaders = [];
                }
    
                // show & buffer first items in the list
                this.topProxy.setHeight(0);
                this.bottomProxy.setHeight(this.store.getCount() * this.maxItemHeight);
                this.renderOnScroll(0); // renders first this.minimumItems nodes in store
            }
        },
    
        // @private - override
        afterRender: function () {
            Ext.ux.BufferedList.superclass.afterRender.apply(this, arguments);
    
            // set up listeners which will trigger rendering/cleanup of our sliding window of items
            this.mon(this.scroller, {
                scroll: this.renderOnScroll,
                scrollend: this.onScrollStop,
                scope: this
            });
    
        },
    
        // @private - queue up tasks to perform on scroll end
        onScrollStop: function () {
    
            // prevents the list from selecting an item if the user just taps to stop the scroll
            if (this.blockScrollSelect && !this.disableSelection) {
                this.selModel.setLocked(true);
                Ext.defer(this.unblockSelect, 100, this);
            }
            // Queue cleanup task.
            // The reason this is a delayed task, rather a direct execution, is that
            // scrollend fires when the user merely flicks the list for further scrolling.
            this.cleanupTask.delay(250);
        },
    
        // @private - delayed task function to resume selection after scroll end
        unblockSelect: function () {
            this.selModel.setLocked(false);
        },
    
        // check if index of store record corresponds to a currently rendered item
        isItemRendered: function (index) {
            // Trivial check after first render
            return this.all.elements.length > 0 ?
                index >= this.topItemRendered && index <= this.bottomItemRendered : false;
        },
    
        // return array of list item nodes actually visible. If returnAsIndexes is true,
        // this will be an array of record indexes, otherwise it will be an
        // array of nodes.
        getVisibleItems: function (returnAsIndexes) {
            var startPos = this.scroller.getOffset().y;
            var elems = this.all.elements,
                nElems = elems.length,
                returnArray = [],
                thisHeight = this.getHeight(),
                node,
                offTop,
                i;
            for (i = 0; i < nElems; i++) {
                node = elems[i];
                offTop = node.offsetTop + node.offsetHeight;
                if (offTop > startPos) {
                    returnArray.push(returnAsIndexes ? node.viewIndex : node);
                    if (offTop - startPos > thisHeight) {
                        break;
                    }
                }
            }
            return returnArray;
        },
    
        // @private - render items into sliding window
        renderOnScroll: function (startRecord) { // startRecord optional
    
            // cancel any cleanups pending from a scrollstop
            this.cleanupTask.cancel();
    
            // if we're still executing a cleanup task, or add/remove/replace, wait
            // for the next call
            if (this.isUpdating) {
                return 0;
            }
    
            if (this.debugFlag) {
                this.isUpdating = false;
            }
    
            var scrollPos = this.scroller.getOffset().y;
    
            var newTop = null,
                newBottom = null,
                previousTop = this.topItemRendered,
                previousBottom = this.bottomItemRendered,
                scrollDown = false,
                incrementalRender = false,
                maxIndex = this.store.getCount() - 1;
    
    
            if (Ext.isNumber(startRecord)) {
                if (startRecord < 0 || startRecord > maxIndex) {
                    return 0; // error
                }
                newTop = startRecord;
                newBottom = Math.min((startRecord + this.minimumItems) - 1, maxIndex);
                scrollDown = true;
                incrementalRender = false;
            }
            else {
                var thisHeight = this.getHeight();
                // position of top of list relative to top of visible area (+above, -below)
                var listTopMargin = scrollPos - this.topProxy.getHeight();
                // position of bottom of list relative to bottom of visible area (+above, -below)
                var listBottomMargin = (scrollPos + thisHeight) - (this.topProxy.getHeight() + this.listContainer.getHeight());
                // scrolled into "white space"
                if (listTopMargin <= -thisHeight || listBottomMargin >= thisHeight) {
                    incrementalRender = false;
                    scrollDown = true;
                    newTop = Math.max((Math.floor(scrollPos / this.maxItemHeight) - 1), 0);
                    newBottom = Math.min((newTop + this.minimumItems) - 1, maxIndex);
                }
                // about to scroll off top of list
                else if (listTopMargin < 50 && this.topItemRendered > 0) {
                    newTop = Math.max(this.topItemRendered - this.batchSize, 0);
                    newBottom = previousBottom;
                    scrollDown = false;
                    incrementalRender = true;
                }
                // about to scroll off bottom of list
                else if (listBottomMargin > -50) {
                    newTop = previousTop;
                    newBottom = Math.min(previousBottom + this.batchSize, maxIndex);
                    scrollDown = true;
                    incrementalRender = true;
                }
            }
    
            // no need to render anything?
            if ((newTop === null || newBottom === null) ||
                 (incrementalRender && newTop >= previousTop && newBottom <= previousBottom)) {
                // still need to update list header appropriately
                if (this.useGroupHeaders && this.pinHeaders) {
                    this.updateListHeader(scrollPos);
                }
                return 0;
            }
    
            var startIdx, nItems = 0;
            // Jumped past boundaries of currently rendered items? Replace entire item list.
            if (this.bottomItemRendered === 0 || !incrementalRender) {
                // new item list starting with newTop
                nItems = this.replaceItemList(newTop, this.minimumItems);
            }
            // incremental - scrolling down
            else if (scrollDown) {
                startIdx = previousBottom + 1;
                nItems = this.appendItems(startIdx, this.batchSize);
            }
            // incremental - scrolling up
            else {
                startIdx = Math.max(previousTop - 1, 0);
                nItems = this.insertItems(startIdx, this.batchSize);
                // collapse top proxy to zero if we're actually at the top.
                // This causes a minor behavioral glitch when the top proxy has
                // non-zero height - the list stops momentum at the top instead of
                // bouncing. But this only occurs when navigating into the middle
                // of the list, then scrolling all the way back to the top, and
                // doesn't prevent any other functionality from working. It could
                // probably be worked around with enough creativity ...
                if (newTop === 0) {
                    this.topProxy.setHeight(0);
                    this.scroller.updateBoundary();
                    this.scroller.suspendEvents();
                    this.scroller.scrollTo({ x: 0, y: 0 });
                    this.scroller.resumeEvents();
                }
            }
    
            // zero out bottom proxy if we're at the bottom ...
            if (newBottom === maxIndex) {
                var bottomPadding = this.getHeight() - this.listContainer.getHeight();
                this.bottomProxy.setHeight(bottomPadding > 0 ? bottomPadding : 0);
            }
    
            // update list header appropriately
            if (this.useGroupHeaders && this.pinHeaders) {
                this.updateListHeader(this.scroller.getOffset().y);
            }
    
            return nItems;
        },
    
        // @private
        updateListHeader: function (scrollPos) {
            scrollPos = scrollPos || this.scroller.getOffset().y;
    
            // List being "pulled down" at top of list. Hide header.
            if (scrollPos <= 0 && this.headerText) {
                this.updateHeaderText(false);
                return;
            }
    
            // work backwards through groupHeaders until we find the
            // first one at or above the top of the viewable items.
            this.headerHeight = this.headerHeight || this.header.getHeight();
            var i,
                headerNode,
                nHeaders = this.groupHeaders.length,
                headerMoveTop = scrollPos + this.headerHeight,
                groupTop,
                transform,
                headerText;
            for (i = nHeaders - 1; i >= 0; i--) {
                headerNode = this.groupHeaders[i];
                groupTop = headerNode.offsetTop;
                if (groupTop < headerMoveTop) {
                    // group header "pushing up" or "pulling down" on list header
                    if (groupTop > scrollPos) {
                        this.transformedHeader = true;
                        transform = (scrollPos + this.headerHeight) - groupTop;
                        Ext.Element.cssTranslate(this.header, { x: 0, y: -transform });
                        // make sure list header text displaying previous group
                        this.updateHeaderText(this.getPreviousGroup(headerNode.innerHTML).toUpperCase());
                    }
                    else {
                        this.updateHeaderText(headerNode.innerHTML);
                        if (this.transformedHeader) {
                            this.header.setStyle('-webkit-transform', null);
                            this.transformedHeader = false;
                        }
                    }
                    break;
                }
            }
            // if we never got a group header above the top of the list, make sure
            // list header represents previous group text
            if (i < 0 && headerNode) {
                this.updateHeaderText(this.getPreviousGroup(headerNode.innerHTML).toUpperCase());
                if (this.transformedHeader) {
                    this.header.setStyle('-webkit-transform', null);
                    this.transformedHeader = false;
                }
            }
        },
    
        // @private
        updateHeaderText: function (groupString) {
            if (!groupString) {
                this.header.hide();
                this.headerText = groupString;
            }
            else if (groupString !== this.headerText) {
                this.header.update(groupString);
                this.header.show();
                this.headerText = groupString;
            }
        },
    
        // @private
        itemCleanup: function () {
            // item cleanup just replaces the current item list with a new, shortened
            // item list. This is much faster than actually removing existing item nodes
            // one by one.
            if (this.all.elements.length > this.cleanupBoundary) {
                this.updateItemList();
            }
        },
    
    
        // used by insertItems, appendItems, replaceItems. Builds HTML to add
        // to list container. Inserts group headers as appropriate.
        // @private
        buildItemHtml: function (firstItem, lastItem) {
            // loop over records, building up html string
            var i,
                htm = '',
                store = this.store,
                tpl = this.itemTplDelayed,
                grpHeads = this.useGroupHeaders,
                record,
                groupId;
            for (i = firstItem; i <= lastItem; i++) {
                record = store.getAt(i);
                if (grpHeads) {
                    groupId = store.getGroupString(record);
                    if (i === this.groupStartIndex(groupId)) {
                        htm += ('<h3 class="x-list-header">' + groupId.toUpperCase() + '</h3>');
                    }
                }
                htm += tpl.applyTemplate(record.data);
            }
            return htm;
        },
    
        // @private - Replace current contents of list container with new item list
        replaceItemList: function (firstNew, nItems) {
            var sc = this.store.getCount();
            if (firstNew >= sc) {
                return 0;
            }
            else if (firstNew + nItems > sc) {
                nItems = sc - firstNew;
            }
    
            // See if the first item is currently rendered. If so, save the
            // exact offset top position so we can recreate it. Otherwise, calculate
            // new proxy size.
            var topProxyHeight,
                firstNode = this.getNode(firstNew);
            if (firstNode) {
                topProxyHeight = firstNew === 0 ? 0 : firstNode.offsetTop;
            }
            else {
                topProxyHeight = firstNew * this.maxItemHeight;
            }
    
            var bottomProxyHeight = (sc - firstNew) * this.maxItemHeight;
    
            // build html string
            var lastNew = (firstNew + nItems) - 1;
            var htm = this.buildItemHtml(firstNew, lastNew);
    
            // replace listContainer internals with new html
            this.all.elements.splice(0);
            this.groupHeaders.splice(0);
            this.listContainer.update(htm);
    
            // append our new nodes to the elements array
            var nodes = this.listContainer.dom.childNodes,
                nodelen = nodes.length,
                firstIndex = firstNew,
                newNode,
                tagName;
            for (var i = 0; i < nodelen; i++) {
                newNode = nodes[i];
                tagName = newNode.tagName;
                if (tagName === 'DIV') {
                    newNode.viewIndex = firstIndex++;
                    this.all.elements.push(newNode);
                }
                else if (tagName === 'H3') {
                    this.groupHeaders.push(newNode);
                }
            }
    
            // reset proxy heights, and save indicies of first and last items rendered
            this.topProxy.setHeight(topProxyHeight);
            this.bottomProxy.setHeight(bottomProxyHeight - this.listContainer.getHeight());
            this.topItemRendered = firstNew;
            this.bottomItemRendered = lastNew;
    
            return nItems;
        },
    
        // Append a chunk of items to list container. Return number of items appended. 
        // @private
        appendItems: function (firstNew, nItems) {
            // check to make sure parameters in bounds
            var sc = this.store.getCount();
            if (firstNew >= sc) {
                return 0;
            }
            else if (firstNew + nItems > sc) {
                nItems = sc - firstNew;
            }
    
            // save current bottom of list, so we know where to start
            // to find our new nodes.
            var oldLastChild = this.listContainer.dom.lastChild;
    
            // save current list container height
            var oldListHeight = this.listContainer.getHeight();
    
            // build html string
            var lastNew = (firstNew + nItems) - 1;
            var htm = this.buildItemHtml(firstNew, lastNew);
    
            // append new nodes
            Ext.DomHelper.insertHtml('beforeEnd', this.listContainer.dom, htm);
    
            // append our new nodes to the elements array
            var tagName, newNode = oldLastChild ? oldLastChild.nextSibling : this.listContainer.dom.firstChild;
            while (newNode) {
                tagName = newNode.tagName;
                if (tagName === 'DIV') {
                    newNode.viewIndex = firstNew++;
                    this.all.elements.push(newNode);
                }
                else if (tagName === 'H3') {
                    this.groupHeaders.push(newNode);
                }
                newNode = newNode.nextSibling;
            }
    
            // recalculate bottom proxy height, and save index of last item rendered
            this.bottomProxy.setHeight(this.bottomProxy.getHeight() - (this.listContainer.getHeight() - oldListHeight));
            this.bottomItemRendered = lastNew;
            return nItems;
        },
    
        // Insert a chunk of items at top of list container. Return number of items inserted.
        insertItems: function (firstNew, nItems) {
            // check to make sure parameters in bounds
            if (firstNew < 0) {
                return 0;
            }
            else if (firstNew - nItems < 0) {
                nItems = firstNew + 1;
            }
    
            // save current top of list, so we know where to start
            // to find our new nodes.
            var oldFirstChild = this.listContainer.dom.firstChild;
    
            // save current list container height
            var oldListHeight = this.listContainer.getHeight();
    
            // build html string
            var lastNew = (firstNew - nItems) + 1;
            var htm = this.buildItemHtml(lastNew, firstNew);
    
            // insert new nodes
            Ext.DomHelper.insertHtml('afterBegin', this.listContainer.dom, htm);
    
            // insert our new nodes into the elements array
            var tagName, newNode = oldFirstChild ? oldFirstChild.previousSibling : this.listContainer.dom.lastChild;
            while (newNode) {
                tagName = newNode.tagName;
                if (tagName === 'DIV') {
                    newNode.viewIndex = firstNew--;
                    this.all.elements.unshift(newNode);
                }
                else if (tagName === 'H3') {
                    this.groupHeaders.unshift(newNode);
                }
                newNode = newNode.previousSibling;
            }
    
            // recalculate top proxy height, and save index of first item rendered
            var newHeight = this.topProxy.getHeight() - (this.listContainer.getHeight() - oldListHeight);
            this.topProxy.setHeight(lastNew === 0 ? 0 : Math.max(newHeight, 0));
            this.topItemRendered = lastNew;
    
            return nItems;
        },
    
        // @private - create a map of grouping strings to start index of the groups
        initGroupIndexMap: function () {
            this.groupIndexMap = {};
            var i,
                key,
                firstKey,
                store = this.store,
                recmap = {},
                groupMap = this.groupIndexMap,
                prevGroup = '',
                sc = store.getCount();
    
            // build temporary map of group string to store index from store records
            for (i = 0; i < sc; i++) {
                key = escape(store.getGroupString(store.getAt(i)).toLowerCase());
                if (recmap[key] === undefined) {
                    recmap[key] = { index: i, closest: key, prev: prevGroup };
                    prevGroup = key;
                }
                if (!firstKey) {
                    firstKey = key;
                }
            }
    
            // now make sure our saved map has entries for every index string
            // in our index bar, if we have a bar.
            if (!!this.indexBar) {
                var barStore = this.indexBar.store,
                    bc = barStore.getCount(),
                    grpid,
                    idx = 0,
                    recobj;
                prevGroup = '',
                    key = '';
                for (i = 0; i < bc; i++) {
                    grpid = barStore.getAt(i).get('key').toLowerCase();
                    recobj = recmap[grpid];
                    if (recobj) {
                        idx = recobj.index;
                        key = recobj.closest;
                        prevGroup = recobj.prev;
                    }
                    else if (!key) {
                        key = firstKey;
                    }
                    groupMap[grpid] = { index: idx, closest: key, prev: prevGroup };
                }
            }
            else {
                this.groupIndexMap = recmap;
            }
        },
    
        // @private - get an encoded version of the string for use as a key in the hash 
        getKeyFromId: function (groupId) {
            return escape(groupId.toLowerCase());
        },
        // @private - get the group object corresponding to the given id
        getGroupObj: function (groupId) {
            return this.groupIndexMap[this.getKeyFromId(groupId)];
        },
    
        // @private - get starting index of a group by group string
        groupStartIndex: function (groupId) {
            return this.getGroupObj(groupId).index;
        },
    
    
        // @private - get group preceding the one in groupId
        getPreviousGroup: function (groupId) {
    
            return this.getGroupObj(groupId).prev;
        },
    
        // @private - get closest non-empty group to specified groupId from indexBar
        getClosestGroupId: function (groupId) {
            return this.getGroupObj(groupId).closest;
        },
    
        // @private
        indexOfRecord: function (rec) {
            // take advantage of group map to speed up search for record index. Speeds up
            // selection slightly.
            var idx = -1, store = this.store, sc = store.getCount();
            if (this.grouped) {
                for (idx = this.groupStartIndex(store.getGroupString(rec)); idx < sc; idx++) {
                    if (store.getAt(idx) === rec) {
                        break;
                    }
                }
            }
            else {
                idx = this.store.indexOf(rec)
            }
            return idx;
        },
    
        // @private - respond to indexBar touch.
        onIndex: function (record, target, index) {
    
            // get first item of group from map
            var grpId = record.get('key').toLowerCase();
            var firstItem = this.groupStartIndex(grpId);
    
            // render new list of items into list container
            if (Ext.isNumber(firstItem) && this.renderOnScroll(firstItem) > 0) {
                // Set list header text to reflect new group.
                if (this.useGroupHeaders && this.pinHeaders) {
                    this.updateHeaderText(this.getClosestGroupId(grpId).toUpperCase());
                }
    
                // scroll list container into view. Temporarily suspend scroll events
                // so as not to invoke another call to renderOnScroll. Must update
                // scroller boundary to make sure scroll position in bounds.
                this.scroller.updateBoundary();
                this.scroller.suspendEvents();
                this.scroller.scrollTo({ x: 0, y: this.topProxy.getHeight() }, false);
                this.scroller.resumeEvents();
            }
        },
    
        // @private - override
        onItemDeselect: function (record) {
            var node = this.getNode(record);
            if (node) {
                Ext.fly(node).removeCls(this.selectedItemCls);
            }
        },
    
        // getNode just compensates for the offset between the record index of
        // our first rendered item and zero.
        // @private - override
        getNode: function (nodeInfo) {
            nodeInfo = nodeInfo instanceof Ext.data.Model ? this.indexOfRecord(nodeInfo) : nodeInfo;
            if (Ext.isNumber(nodeInfo)) {
                return this.isItemRendered(nodeInfo) ?
                    this.all.elements[nodeInfo - this.topItemRendered] : null;
            }
            return Ext.ux.BufferedList.superclass.getNode.call(this, nodeInfo);
        },
    
        // @private - called on Add, Remove, Update, and cleanup.
        updateItemList: function () {
            // Update simply re-renders this.minimumItems item nodes, starting with the first visible
            // item, and then restores any item selections. The current scroll position
            // of the first visible item will be maintained.
            this.isUpdating = true;
            var visItems = this.getVisibleItems(true);
            var startItem = visItems.length ? visItems[0] : 0;
            // save selections
            var selectedRecords = this.getSelectedRecords();
            // replace items
            this.replaceItemList(startItem, this.minimumItems);
            // restore selections
            var i, node;
            for (var i = 0; i < selectedRecords.length; i++) {
                node = this.getNode(selectedRecords[i]);
                if (node) {
                    Ext.fly(node).addCls(this.selectedItemCls);
                }
            }
            this.isUpdating = false;
        },
    
        // each of the data store modifications is handled by the updateItemList
        // function, which will ensure that the currently visible items reflect
        // the latest state of the store.
        // @private - override
        onUpdate: function (store, record) {
            if (this.grouped) {
                this.initGroupIndexMap();
            }
            this.updateItemList();
        },
    
        // @private - override
        onAdd: function (ds, records, index) {
            if (this.grouped) {
                this.initGroupIndexMap();
            }
            this.updateItemList();
        },
    
        // @private - override
        onRemove: function (ds, record, index) {
            if (this.grouped) {
                this.initGroupIndexMap();
            }
            this.updateItemList();
        },
    
        onBeforeHide: function () {
            // Stop the scroller when this component is hidden, e.g. when switching
            // tabs in a tab panel.
            var sc = this.scroller;
            sc.suspendEvents();
            sc.scrollTo({ x: 0, y: sc.getOffset().y });
            sc.resumeEvents();
            return true;
        }
    
    
    });
    Ext.reg('bufferedlist', Ext.ux.BufferedList);
    I've added an function to reset the scrollposition, this helps a lot when filtering the list.
    Last edited by Nikkelmann; 28 Jul 2011 at 11:41 PM. Reason: Fixed?

  10. #60
    Sencha User
    Join Date
    Feb 2011
    Posts
    104
    Vote Rating
    0
    headkit is on a distinguished road

      0  

    Default


    *subscribe*

Similar Threads

  1. tobiuGrid - High Performance EditorGrid
    By tobiu in forum Community Discussion
    Replies: 23
    Last Post: 21 Dec 2010, 8:10 PM
  2. ExtJS Grid, Poor Performance with High Frequency Updates?
    By pkoa in forum Ext 3.x: Help & Discussion
    Replies: 3
    Last Post: 17 Sep 2010, 5:36 AM
  3. Interesting high performance grid
    By mankz in forum Community Discussion
    Replies: 7
    Last Post: 21 Aug 2010, 1:59 PM
  4. ExtJS performance on large forms
    By berend in forum Ext 2.x: Help & Discussion
    Replies: 5
    Last Post: 12 May 2010, 5:54 AM
  5. [FIXED] [1.1.4] ComboBox PagingToolBar to high in the dropdown list
    By mwojciechowski in forum Ext GWT: Bugs (1.x)
    Replies: 3
    Last Post: 26 Nov 2008, 9:12 PM

Thread Participants: 45