PDA

View Full Version : List performance consolidation thread



MahlerFreak
20 Dec 2010, 10:35 AM
I'm building a music control application, consisting of a dedicated LAN-only HTTP server, and Sencha-based clients. The nature of this application means that I must deal gracefully with lists of data potentially in the 10,000 to 100,000 item range, with a high degree of interactivity. There have been several threads commenting that list performance, especially scrolling performance, degrades badly as the number of items goes up. So I'm starting this thread as a way of consolidating tips and strategies to dealing with large lists.

Two general approaches to take are 1) minimize the number of items displayed, and/or 2) investigate alternative scrolling algorithms.

The first approach is to minimize the number of items actually displayed in the list at any one time. My experiments show that scrolling performance is almost entirely a function of the number of nodes actually displayed in the scrolling div. So if we hide the list elements that are well off the top or bottom of the list viewport, and then replace the space those items take with a blank spacer div, we'll maintain scrolling performance for the items actually in front of the user. Of course, this means we have to update the items that are hidden/displayed, and the size of those top and bottom spacers, when the list gets scrolled.

So the markup structure inside the list component will look like:


<div class="top-list-spacer" height="(number of hidden items at top) * (height of list item)"></div>
<div class="x-list-item" style="display:none">/*hidden list item*/</div>
/*repeat hidden list items at top of list*/
<div class="x-list-item" style="display:block">/*visible list item*/</div>
/*repeat visible list items*/
<div class="x-list-item" style="display:none">/*hidden list item*/</div>
/*repeat hidden list items at top of list*/
<div class="bottom-list-spacer" height="(number of hidden items at bottom) * (height of list item)"></div>


How do the spacer divs get there? Well, either by modifying the main list template (not the item template), or by injecting them after rendering, using a render listener. In any case, to keep this structure up to date, we have to know when the user has scrolled above the top or below the bottom of our displayed items (they have scrolled into the areas of the blank spacer divs). This means listening for "scrollend" events from the list scroller. Code outline:


var myList = new Ext.List({...});
myList.mon(myList.scroller,
{
scrollend: handleScrollEnd,
scope:myList
}
);
var handleScrollEnd = function() {
// Note: "this" inside this function refers to my list component
// Compute how many list items are past the top of the list viewport
.................
// Hide items that are too far past top of list viewport
.................
// Set height of top spacer div to match height of top list items just hidden
.................
// Compute how many list items are past the bottom of the list viewport
.................
// Hide items that are too far past the bottom of list viewport
.................
// Set height of bottom spacer div to match height of bottom list items just hidden
.................
}

There are obvious variations and extensions of this first strategy that could be done. For instance, instead of rendering the whole list up front and then hiding/displaying, we could render just the items that are visible at any one time, and even request just those items from the server. This is what I have to do, to minimize data transfer overhead. Also, instead of rendering single blank spacer divs, you could render several divs that provide "hints" to the user as to where they are in the blank parts of the list - which is what I do in my alphabetically grouped lists.

If people are interested, and willing to contribute their own ideas and feedback, I'm willing to spend a couple of days creating more generic working versions of this kind of list extension.

The second strategy, investigating other kinds of scrolling algorithms, is not something I've done yet. I know the Sencha team has already put in quite a bit of time here. But I'd also say, check out the "List Performance" example on jquerymobile.com:

http://jquerymobile.com/demos/1.0a2/#docs/lists/lists-performance.html

The rendering is very slow. The scrolling is very responsive, but very unnatural. There are obviously some tradeoffs around this, and some more investigation might lead to a set of tradeoffs that work better for large lists.

EDIT: I screwed up. The above jquery mobile example does not do local scrolling at all - the "scrolling" is simply native scrolling of the entire document, not an element within the document. So there is very likely no magic to be found in changing the scrolling algorithm.

mherger
20 Dec 2010, 1:08 PM
Very interesting. Today, while waiting at the train station, I've been chewing this very issue, and I had the same idea (no tests yet, just been thinking). Do you have some working code?

One other approach to reduce load time is dynamically rendering items. It's helping a lot, but not enough for my taste. See http://www.sencha.com/forum/showthread.php?116604-Ext.List-optimise-itemTpl-for-speed&p=547877#post547877. What I'm doing there is render empty items of a fixed height, but render every item. Then render the full content in a sliding window of items while scrolling. It greatly helped performance, but still won't scale to > 1000 items, as even with a simplified item structure the DOM becomes too large.

Maybe some of my code might become useful to your approach too.

MahlerFreak
20 Dec 2010, 2:36 PM
mherger,

Yes, I'm working right now on a custom component grouped list that dynamically renders. If everything goes well, it should be ready in a couple of hours. We can exchange code when I post it up :) As you have found, I've found that simply rendering empty items doesn't help the performance as much as I'd like. Instead, I'm using a single "proxy" item to represent a group that isn't rendered - you'll see what I mean when I post my extension later.

Scott

Shijutv
20 Dec 2010, 8:39 PM
Hi MahlerFreak (http://www.sencha.com/forum/member.php?9548-MahlerFreak),
I have a list component with about 1000 items . Can you help me to implement the above things ?

iGlossary.views.AlphabeticList = Ext.extend(Ext.List, {
singleSelect: true,
grouped: true,
indexBar: true,
onItemDisclosure:true,
initComponent: function() {
this.store = iGlossary.stores.AlphabeticItems;
this.itemTpl = '{term}';
iGlossary.views.AlphabeticList.superclass.initComponent.call(this);
},
show: function(){
this.scroller.on('scrollend', function (comp, offset) {

});
},
hide: function(){
this.scroller.un('scrollend', function (comp, offset) {

});
}
});

Shiju

konki_vienna
21 Dec 2010, 5:04 AM
I am very much curious on MahlerFreak's solution and hope that it can be applied to SenchaTouch-Lists as well... Good luck with that! :) It is great, that you will share the fruits of your brain with the community!

MahlerFreak
21 Dec 2010, 12:42 PM
OK, here is a custom grouped list that manages scroll performance by rendering only one group of items at a time. The other groups are represented by a single proxy item. If you scroll or use the index bar to go to a new group, that group becomes the rendered group. If you're scrolling and tap on one of the proxy groups to stop the scroll, it becomes the rendered group. You can obviously scrub up and down on the index bar to get to any one particular group.

The performance with the provided example is quite good on iPad, OK on iPod touch, not too great on Samsung Galaxy S, brilliant on desktop Chrome :) The code below is quite functional, but still has some things to be finished (I think item selection probably won't work correctly as is - it will give the wrong record/index) and some other bugs to work out. But I'm not going to spend much more time on this until I get some feedback from folks - does this get enough performance? Is the user interaction acceptable, or not? It certainly is a non-standard way to interact with lists on these devices, and may be too confusing. Be completely honest; I don't want to put more work into something people won't use.

The code for the custom list component is below. It is quite copiously commented, so you can see what's
going on.

EDIT: updated the code to reflect bug fixes in rev 2



Ext.namespace('Ext.ux.touch');


Ext.ux.touch.GroupRevealList = Ext.extend(Ext.List,{

initComponent: function() {



// This is a grouped list only, regardless of config options
this.grouped = true;

// We're going to replace the default group template with our own special version.
// It's not much more complicated than the original. Only the list items inside
// the group specified by the "activeRenderGroup" member of the template will be
// actually be rendered; the rest of the groups are each represented by a single "proxy"
// item.
this.groupTpl = [
'<tpl for=".">',
'<tpl if="this.shouldRenderItems(group)">',
'{[this.incRenderCount(values.nitems)]}',
'<div class="x-list-group x-group-{id}">',
'<h3 class="x-list-header">{group}</h3>',
'<div class="x-list-group-items">',
'{items}',
'</div>',
'</div>',
'</tpl>',
'<tpl if="!this.itemsRendered">',
'{[this.incStartCount(values.group,values.nitems)]}',
'<div class="x-list-group x-group-{id}">',
'<h3 class="x-list-header">{group}</h3>',
'<div class="x-list-group-items x-list-group-proxy" groupProxyId="{group}">',
'{[values.group + "..."]}',
'</div>',
'</div>',
'</tpl>',
'</tpl>'
];

// Save the super class in an object variable. This saves both typing, and
// two levels of object dereferencing when calling superclass functions.
this.mySuper = Ext.ux.touch.GroupRevealList.superclass;

// superclass constructor will now set up the render template including our
// new group template. It will also create our index bar, if any.
this.mySuper.initComponent.call(this);

// add a member to this object to save the number of items not rendered
// before the start of rendered items. This offset is needed to make
// selection work - see below.
this.renderIndexOffset = 0;

// Create the active group member in our render template
// Add some new data members and functions to our template,
// to be used by our new group template above. It is simpler
// to add them here, with the template object otherwise complete, than
// to figure out how to add them to the complicated process of
// building this template in DataView and List.
this.tpl.activeRenderGroup = "A";
this.tpl.nRendered = 0;
this.tpl.startRenderIndex = 0;
this.tpl.itemsRendered = false;
this.tpl.shouldRenderItems = function(group) {
this.itemsRendered = (group >= this.activeRenderGroup && this.nRendered < 20);
return this.itemsRendered;
}
this.tpl.incRenderCount = function(nitems) {
this.nRendered += nitems;
return "";
}
this.tpl.incStartCount = function(group,nitems) {
if ( group < this.activeRenderGroup ) {
this.startRenderIndex += nitems;
}
return "";
}
this.tpl.prepareNewRender = function(group) {
this.activeRenderGroup = group;
this.nRendered = 0;
this.startRenderIndex = 0;
this.itemsRendered = false;
}


},

// we override this purely to be able to collect the number of children in a group as part of
// the data passed to our template. Otherwise, this is an exact copy of List:collectData
collectData : function(records, startIndex) {
if (!this.grouped) {
return this.mySuper.collectData.call(this, records, startIndex);
}

var results = [],
groups = this.store.getGroups(),
ln = groups.length,
children, cln, c,
group, i;

for (i = 0, ln = groups.length; i < ln; i++) {
group = groups[i];
children = group.children;
for (c = 0, cln = children.length; c < cln; c++) {
children[c] = children[c].data;
}
results.push({
group: group.name,
id: this.getGroupId(group),
// This is our mod
nitems: cln,
items: this.listItemTpl.apply(children)
});
}

return results;
},

// override of inherited list function - need to monitor scrollend
// we need to update our list rendering any time scrolling ends, or
// any time the user has tapped or scrubbed our index bar.
initEvents: function() {
// call base class method
this.mySuper.initEvents.call(this);

// monitor scroll end
this.mon(this.scroller,
{
scrollend: this.onScrollEnd,
scope: this
}
);

// monitoring the index bar is a bit tricky. We don't want to re-render
// every time the index changes, only when the listener ends his touch
// on the desired index. What makes this tricky is that the index bar
// itself listens for touchend, and stops the event from reaching us.
// So we create a sequence instead, and tell the index bar to call that
// sequence on touchend.
if ( this.indexBar ) {
var bar = this.indexBar;
// remove existing touchend listener on index bar
bar.mun(bar.el,'touchend',bar.onTouchEnd);
// create function sequence
bar.onTouchEnd = Ext.createSequence(bar.onTouchEnd,this.onLastIndex,this);
// and re-establish the touchend listener on the index bar
bar.mon(bar.el,
{
touchend: bar.onTouchEnd,
scope: bar
}
);
}

},

// overriden method - if tap was on a group proxy element, make that the
// current rendered group.
onTap: function(e) {
var proxyEl, groupId;
// clear any pending updates
if ( this.scrollEndTimer ) {
clearTimeout(this.scrollEndTimer);
this.scrollEndTimer = null;
}
// check if this is tap on proxy group element. If so, make that
// group the current rendered group.
proxyEl = e.getTarget('.x-list-group-proxy', this.getTargetEl());
if ( proxyEl ) {
groupId = proxyEl.getAttribute('groupProxyId');
if ( groupId ) {
this.setRenderGroup(groupId);
}
}
else {
this.mySuper.onTap.apply(this,arguments);
}
},

// Override of inherited Ext:List function. If the user has suddenly flicked the scroller
// again, cancel any pending render of the new group.
onScrollStart: function() {
if ( this.scrollEndTimer ) {
clearTimeout(this.scrollEndTimer);
this.scrollEndTimer = null;
}
this.mySuper.onScrollStart.call(this);
},

// This gets called when the scroll ends. Must check if there is a new group
// visible, and render it if so. We delay execution of this, so that the
// user can flick the list some more, or change scroll direction.
onScrollEnd: function() {
if ( this.scrollEndTimer ) {
clearTimeout(this.scrollEndTimer);
this.scrollEndTimer = null;
}
this.scrollEndTimer = Ext.defer(this.deferredScrollEnd,300,this);
},

// do the acutal work of rendering a new group on scrollend.
deferredScrollEnd: function() {
// null out the scroll timer - it is no longer valid since we have already gotten here.
if ( this.scrollEndTimer ) {
this.scrollEndTimer = null;
}
// try/catch/finally block to make sure any errors don't kill our scroll events
// permanently.
try {
// ignore scrolling events while rendering is happening
this.scroller.suspendEvents();
// scoller position
var scrollPos = this.scroller.getOffset();
// height of list viewport
var vpHeight = this.getTargetEl().getHeight();
// what are the group(s) in view?
var closest = this.getClosestGroups(scrollPos);
// which group should we render? The one that takes up more of the
// screen ...
var group;
if ( closest.current.offset >= scrollPos.y ) {
group = closest.current;
}
else if ( closest.next && (closest.next.offset - scrollPos.y) < (vpHeight/2) ) {
group = closest.next;
}
else {
group = closest.current;
}
// get the id of the group to update
var groupId = group.header.getHTML();
// render the group to update (does nothing if group is the same as previous)
this.setRenderGroup(groupId);
}
catch(e) {
this.scroller.resumeEvents();
throw(e);
}
finally {
this.scroller.resumeEvents();
}
},

// override of inherited Ext:List function
onIndex: function(record,target,index) {
// save the last index record, to use when the user ends touch
// on the index bar
this.lastIndexRecord = record;
// call overridden base class method
this.mySuper.onIndex.apply(this,arguments);
},

// this gets called after the user has finished tapping or scrubbing on the index bar
onLastIndex: function() {
if ( this.lastIndexRecord ) {
// see which group we've just indexed to
var groupId = this.lastIndexRecord.get('key').toUpperCase();
this.lastIndexRecord = null;
// render the group (does nothing if group is the same as previous)
this.setRenderGroup(groupId);
}
},

// We need to create our own, overridden version of updateIndexes (from DataView)
// Our version corrects the index for all of the items not rendered.
updateIndexes : function(startIndex, endIndex){
var indexOffset = this.tpl.startRenderIndex || 0;
var ns = this.all.elements;
startIndex = startIndex || 0;
endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
for(var i = startIndex; i <= endIndex; i++){
ns[i].viewIndex = i + indexOffset;
}
},

// Unfortunately, we also need to override getNode, which needs to account
// for the offset in render index also
getNode : function(nodeInfo) {
if ( nodeInfo instanceof Ext.data.Model ) {
var idx = this.store.indexOf(nodeInfo);
return this.all.elements[idx - this.renderIndexOffset];
}
return this.mySuper.getNode.call(this,nodeInfo);
},

// As the name says, set the active group using the group string as identifier
setActiveGroupById: function(groupId) {
var groups = this.groupOffsets;
var ngrp = groups.length;
var i, group;
for ( i = 0; i < ngrp; i++ ) {
group = groups[i];
if ( group.header.getHTML() == groupId )
break;
}
if ( group )
this.setActiveGroup(group);
},

// Do the actual work of re-rendering a new active group. Trivially simple at
// this point. Returns true if new rendering was done, false otherwise.
setRenderGroup: function(/*string*/groupId) {
if ( groupId != this.tpl.activeRenderGroup ) {
this.tpl.prepareNewRender(groupId);
// calling refresh() causes the list to be re-rendered, with the new
// group set. refresh() also does some extra work we don't need, but
// it will do for now ...
this.refresh();
// call updateOffsets and updateBoundary to compensate for the new dimensions
// of the group div, and the overall list div
this.updateOffsets();
this.scroller.updateBoundary();
// scroll to the top of the newly rendered group
this.scrollToGroup(groupId);
// set the active group
this.setActiveGroupById(groupId);
// save the render index offset
this.renderIndexOffset = this.tpl.startRenderIndex;
return true;
}
return false;
},

// Scroll to the top of the specified group. Code stolen from List:onIndex function.
scrollToGroup: function(/*string*/groupId) {
var closest = this.getTargetEl().down('.x-group-' + groupId.toLowerCase());
if (closest) {
this.scroller.scrollTo({x: 0, y: closest.getOffsetsTo(this.scrollEl)[1]}, false, null, true);
}
}

});


The class "x-list-group-proxy" is used to style group proxy items; it is defined in the sample code below
as follows:



.x-list-group-proxy {
height: 500px;
text-align:center;
line-height:500px;
font-size:5em;
}


The attached zip file contains a working example, with about 1400 items defined. You'll have to edit listplug.html to point to the correct include paths for your sencha-touch 1.01 library.

Shijutv
21 Dec 2010, 10:55 PM
Hi MahlerFreak, (http://www.sencha.com/forum/member.php?9548-MahlerFreak)

First of all , i want to thank you for your great effort.
I have just integrated this with my project.it is working good with high performance.
There are some bugs we to fix,
As you told when i click an item it fetches a wrong item
When we scroll to another group we have seen the detailed group but in the bottom we again see the same group

Shiju

mherger
22 Dec 2010, 12:06 AM
Thanks a lot MahlerFreak! You've taken a slightly different approach than I would have, but your code reveals some great ideas. And I learned about sequences :-).

I don't want to restrict myself to lists with index bars, as I don't always have them. Maybe I'll just add a numerical index bar at some point :-). But for now my simplification is a unique item height, which allows to do the buffer calculation as you suggested it in your initial posting. And I'm hiding the group headings, as otherwise the calculation becomes a bit more complex too.

I'll spend some time trying to figure out what exactly you're doing and how I could re-use some of this in my own solution. I'll let you know. Thanks a lot!

konki_vienna
22 Dec 2010, 12:31 AM
I tested your zip on my HTC Desire HD - performance is amazing - no screen-freezing anymore...
On the iPad it works good as well, but on an iPod (2nd Generation, iOS 4) normal scrolling (without using the index) does not work good at all: the list does not move when you move your finger away from the screen...

I'll try to take a look at the code within the next days - right know it seems to be magic mysteries to me... Do you think it can be adapted for list without index bar and with scroll-bar instead (the problem here probably is that you do not know the length of the total list, which is crucial for the length of the scroll-bar).

konki_vienna
22 Dec 2010, 5:13 AM
Yes, of course it is possible to not show the index bar: 'indexBar: false' :)

Why does every group, that is rendered with content ('this.shouldRenderItems(group)') appear underneath the rendered group again ('!this.shouldRenderItems(group)')? - Is there a simple way to fix this 'bug'? (Perhaps I'll find out by myself, when I try to implement your version into my application...)

MahlerFreak
22 Dec 2010, 8:19 AM
I'll go to work on the "extra group" bug. I'm sure it is very simple.

EDIT: fixed that bug, fixing some others, will post new version soon.

MahlerFreak
22 Dec 2010, 11:02 AM
Here is a new version of the custom component, which fixes the following bugs:

- Extra proxy group was rendering after the list items for that group
- First group did not render when scrolled back to the top
- Selection was not working.
- Group header was sometimes displaying incorrectly.

Simply replace your current version of listrange.js

I'm going to work on another couple of small enhancements to this. Let me know if you all find any other problems.

After wrapping this up, I'm going to work on a non-grouped list like mherger. I'm also going to experiment with some shallower node structures (e.g. <ul><li>) to minimize the total number of nodes, and see if that speeds things up.

mherger
22 Dec 2010, 1:46 PM
So... here's my approach for a BufferedList class. It's conceptually different from MahlerFreak's approach, but this doesn't hurt :-)

Pros:
- almost seamless dynamic rendering of items, no display of proxy elements, no jumping
- working with or without index bar

Cons:
- requires list items to be of fixed height
- no group headers displayed
- more complex code...

The idea is to only have three items in the list: topProxy, listContainer, bottomProxy. The listContainer is dynamically filled with the items to be shown, while the two proxies at the same time are stretched/shrunk to make the scroller happy.

There is some code to clean up items which are no longer displayed. This helps keeping the DOM size down, but is not strictly necessary. Without it, scrolling down (without using the indexbar) would result in degrading performance.

Oh, and don't forget to adjust file paths in the html file to match your sencha touch folders.

Please let me know what you think about it.

The attached sample uses the same data file from MahlerFreak (thanks :-)).

Have fun!

MahlerFreak
22 Dec 2010, 4:51 PM
mherger,

Brilliant! Works beautifully. I never would have assumed, in a million years, that you could render items fast enough to stay ahead of the scrolling, but that is why assumptions are so bad :) I'd recommend everyone reading this thread to try this immediately.

My experiments today with the approach I laid out in my initial post were not especially promising. Hiding/unhiding already rendered elements doesn't seem to gain as much performance as I'd expected, and it seems to cause iOS/Safari to get confused sometimes.

I've spent a couple of hours this afternoon digging through the Sencha scrolling code. I've noticed some things about the scrolling behavior that are interesting:

- The first scroll in a large list tends to "stick" for an extended period before finally moving.
- If you just scroll the list back and forth with your finger, without lifting, the scrolling tracks your finger very well, even in a large list. This is especially true on iOS.
- As soon as you release your finger, and allow the scrolling to go into "momentum" mode, the scrolling briefly sticks again.
- The scrolling with momentum is much choppier than the scrolling when it tracks your finger.

Anyway, I think there might be some ways to mitigate the problems above. The scrolling code is a bit "dense", and there's some math in there - makes me nostalgic for my old days as a 3D CAD developer. But there is no obvious reason why the "momentum" code should not scroll as smoothly as the interactive, touchmove code, and I'm going to try to find a better solution.

benjamin.jaton
22 Dec 2010, 5:30 PM
MahlerFreak,

Interesting, do you know if it is possible to deactivate this momentum behavior ?

The "first times sticks" are compilation times.
Also I can see that the zipfile is using the -debug.js . You may want to use the actual sencha-touch.js instead.
see http://www.sencha.com/forum/showthread.php?104155-FIXED-131-Android-Scrolling-Issues/page2

MahlerFreak
22 Dec 2010, 6:17 PM
benjamin,

Thank you very much, I suspected already that the initial scroll might be related to JIT compilation, but your link confirms it. There are some obvious hacks that could be done to work around this, increasing load time slightly in exchange for better interaction on the first scroll.

The "hitch" in scrolling when switching over to momentum mode remains - possibly due to the built-in delay in starting the chain of deferred momentum handlers. I don't believe turning off momentum is an option for most developers - it causes scrolling to stop the instant you lift your finger, which is not how native applications behave on these devices ...

konki_vienna
23 Dec 2010, 12:09 AM
You are amazing, guys!
I just lean back and enjoy your posts - and test your code of course. Till I can say something about it, I just raise my hands and clap & cheer loud for you! :)
Thank you!

mherger
23 Dec 2010, 12:48 AM
Brilliant! Works beautifully. I never would have assumed, in a million years, that you could render items fast enough to stay ahead of the scrolling, but that is why assumptions are so bad :) I'd recommend everyone reading this thread to try this immediately.

I'm glad you like it. I'm sure it's still rather buggy, as I've only worked around the issues I've trapped into myself so far. Eg. the index calculation sometimes can be confusing when fetching nodes/records. Those methods probably need more tweaking.


I've spent a couple of hours this afternoon digging through the Sencha scrolling code. I've noticed some things about the scrolling behavior that are interesting:

- The first scroll in a large list tends to "stick" for an extended period before finally moving.

Might be this issue I've reported here? http://www.sencha.com/forum/showthread.php?118823-OPEN-667-1.0.1a-list-items-overlapping-at-the-beginning-of-a-scroll-action

konki_vienna
23 Dec 2010, 3:55 AM
Hi mherger,
do you think with your solution it will be possible to display group headers?

How could a possible solution for this look like?
You would have to know (=calculate) how many group headers are in total, above your current screen-view and below your current screen-view in order to adapt the heights of the top-/bottomProxies. Do you think this could work?

It would be a marriage of the two different approaches (yours and MahlerFreaks) of handling massive amounts of list data, at least feature wise.
And it would be a huge step for the community! :)

MahlerFreak
23 Dec 2010, 1:04 PM
mherger,

Here is a simple fix for the indexing issues. We simply assign the record index to the viewIndex attribute as soon as we create a new item. We update the all.elements list as nodes are created. We keep track of the first item rendered as we render new items. Calls to updateIndexes, in renderItems and cleanup, are no longer needed and thus removed. The override of getRecord is no longer required, and getNode is altered as shown. All of my changes are commented with "SMB". This seems to fix the incorrect indexing of records on select, and generally provides more insurance that things will stay in sync.

EDIT: Just added another fix. I had tried to remove base class scroll event handlers in your initScroller, but they obviously got added in later on, and the base class onScroll was throwing exceptions. So I moved the unregistration of these events to an overridden initEvents - problem solved. Changes reflected in the code below.




Ext.namespace("Ext.ux");

Ext.ux.BufferedList = Ext.extend(Ext.List, {
initComponent: function() {
this.itemTplDelayed = new Ext.XTemplate('<div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div></div>').compile();

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>'
]);


this.on({
afterrender: this.initScroller,

// stop our delayed tasks before we leave the list
beforedeactivate: function() {
if (this.cleanupTask) this.cleanupTask.cancel();
},

// initialize our proxies and the list container
refresh: function() {
if (!this.topProxy)
this.topProxy = this.getTargetEl().down('.ux-list-top-proxy');

if (!this.bottomProxy)
this.bottomProxy = this.getTargetEl().down('.ux-list-bottom-proxy');

if (!this.listContainer)
this.listContainer = this.getTargetEl().down('.ux-list-container');
},

scope: this
});
},

// 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'
}];
},

// SMB - override so we can remove base class scroll event handlers
initEvents: function() {
Ext.ux.BufferedList.superclass.initEvents.call(this);
// SMB - remove listeners added by base class, they waste resources
// and may cause trouble
this.mun(this.scroller, {
scrollstart: this.onScrollStart,
scroll: this.onScroll,
scope: this
});

},

initScroller: function() {
this.visibleItemCount = Math.ceil(this.container.getHeight() / this.itemHeight);

// set up some listeners which will trigger rendering of our items
this.scroller.on({
scrollend: this.renderItems,
scroll: this.renderItems,
scope: this
});

// DelayedTask to cleanup DOM
this.cleanupTask = new Ext.util.DelayedTask(this.cleanup, this);

// show & buffer first items in the list
this.topProxy.setHeight(0);
this.bottomProxy.setHeight(this.store.getCount() * this.itemHeight);
this.renderItems(this.scroller, {x:0, y:0});
},

// calculate which elements from our store should be displayed in the current viewable window
getVisibleItems: function() {
var count = this.visibleItemCount,
sc = this.scroller.getOffset().y,
start = ( sc === 0 ? 0 : (Math.floor(sc/this.itemHeight)-1) );

return {
first: Math.max(start, 0),
last: Math.min(start + count, this.store.getCount()-1)
};
},

isItemRendered: function(index) {
var y = index * this.itemHeight;

// index is before the last rendered item
if ( y < (this.topProxy.getHeight() + this.listContainer.getHeight()) ) {
// index is before the first rendered item
if ( y >= this.topProxy.getHeight() )
return true;
}
},

renderItems: function(scroller, offset) {
var visibleItems = this.getVisibleItems();

// don't process anything if the currently visibleItems haven't changed
if (this.firstItemRendered == visibleItems.first)
return;

// clear our list if we're jumping beyond the alread rendered part of our list
var lastItemRendered = (this.topProxy.getHeight() + this.listContainer.getHeight()) / this.itemHeight;

if (visibleItems.first > lastItemRendered || visibleItems.last < this.firstItemRendered) {
var topHeight = visibleItems.first * this.itemHeight;
var bottomHeight = this.store.getCount() * this.itemHeight - topHeight;

// adjust proxy heights
this.topProxy.setHeight(topHeight);
this.bottomProxy.setHeight(bottomHeight);

// wipe the container
this.listContainer.update('');

// SMB - this would cause an immediate exception in the next
// if clause, and does not seem necessary in any case.
// delete this.firstItemRendered;
}

// scrolling up
if (visibleItems.first < this.firstItemRendered) {
for (var x = visibleItems.last; x >= visibleItems.first; x--) {
this.insertItem(x);
// SMB - assign first item rendered immediately after render,
// to keep things in sync. firstItemRendered is used in
// getNode, below.
this.firstItemRendered = x;
}
}
// scrolling down
else {
// SMB - move here
this.firstItemRendered = visibleItems.first;
for (var x = visibleItems.first; x <= visibleItems.last; x++) {
this.appendItem(x);
}
}


// SMB - handled in appendItem and insertItem
// this.all.fill(Ext.query(this.itemSelector, this.listContainer.dom));
// this.updateIndexes(0);

// remove stale items in a few ms - helps us keep the DOM small and the list fast
this.cleanupTask.delay(500, this.cleanup, this);

return visibleItems;
},

appendItem: function(index) {
if (!this.isItemRendered(index)) {
var newNode = this.itemTplDelayed.append(this.listContainer, this.store.getAt(index).data);
// SMB - add viewIndex property to new node immediately
newNode.viewIndex = index;
// SMB - add newNode to end of elements immediately
this.all.elements.push(newNode);
this.bottomProxy.setHeight(this.bottomProxy.getHeight() - this.itemHeight);
}
},

insertItem: function(index) {
if (!this.isItemRendered(index)) {
var newNode = this.itemTplDelayed.insertFirst(this.listContainer, this.store.getAt(index).data);
// SMB - add viewIndex property to new node immediately
newNode.viewIndex = index;
// SMB - add newNode to front of elements immediately
this.all.elements.unshift(newNode);
this.topProxy.setHeight(this.topProxy.getHeight() - this.itemHeight);
}
},

cleanup: function() {
if (this.visibleItemCount > 0) {
var visibleRows = this.getVisibleItems();

// if first is less than 0, all items have been rendered
// so lets clean the end...
var i = 0;
if (visibleRows.first <= 0) {
i = visibleRows.last + 1;
}

var items = Ext.DomQuery.select('div.x-list-item', this.listContainer.dom);
var offset = this.topProxy.getHeight() / this.itemHeight;

for (var len = items.length; i < len; i++) {
var index = offset + i;
if (index < visibleRows.first) {
this.topProxy.setHeight(this.topProxy.getHeight() + this.itemHeight);
Ext.removeNode(items[i]);
}
else if (index > visibleRows.last) {
this.bottomProxy.setHeight(this.bottomProxy.getHeight() + this.itemHeight);
Ext.removeNode(items[i]);
}
}
// SMB - should probably change this list on the fly, above, to avoid getting
// out of sync.
this.all.fill(Ext.query(this.itemSelector, this.listContainer.dom));
// SMB - not needed any longer
// this.updateIndexes(0);
}
},

// XXX - overwrite existing methods to fix our offset
getNode : function(nodeInfo) {
if (nodeInfo instanceof Ext.data.Model) {
// SMB - use firstItemRendered as index offset.
var idx = this.store.indexOf(nodeInfo);
return this.all.elements[idx-this.firstItemRendered];
}
return Ext.ux.BufferedList.superclass.getNode.call(this, nodeInfo);
},

/* getRecord: function(node) {
var offset = 0;
if (this.topProxy)
offset = this.topProxy.getHeight() / this.itemHeight;

return this.store.getAt(node.viewIndex + offset);
},
*/
// - end offset fixes

// need to overwrite index bar handler: calculate the offset based on the item count
onIndex: function(record, target, index) {
var key = record.get('key').toLowerCase(),
groups = this.store.getGroups(),
ln = groups.length,
group, i, closest, id, firstItem = 0;

for (i = 0; i < ln; i++) {
group = groups[i];
id = this.getGroupId(group);

if (id == key || id > key) {
closest = id;
break;
}
else {
firstItem += group.children.length
closest = id;
}
}

if (closest) {
this.scroller.scrollTo({x: 0, y: firstItem * this.itemHeight}, false, null, true);
}
}
});

mherger
23 Dec 2010, 1:33 PM
Thanks a lot MahlerFreak! It's late over here. I'll have to review your suggestions when I've had some sleep.

BTW: no need to unregister the onScroll handler if you set pinHeaders = false. As we're not showing the headers anyway, this makes sense

mherger
23 Dec 2010, 1:42 PM
do you think with your solution it will be possible to display group headers?

Heh... this is software, anything can be done ;-).

Seriously: I'm sure it's possible. But I guess the complexity would be adding a performance hit again. Looking at my previous comment and what MahlerFreak did we're already getting rid of a lot of processing which is only needed with the headers. The simplicity of my approach is the fact that we _must_ be using items with a fixed height. Otherwise the calculations would be very hard to do. If we added the headers, we'd have to take them into account to. But it's not always easy to know how many headers there are at all, how many of them are shown, how many hidden above/below etc. I will not investigate this, as I don't need it. I'd rather like to keep the current solution as fast as possible.

MahlerFreak
23 Dec 2010, 9:24 PM
mherger,

Sorry to say, further testing reveals that my changes introduce other problems. In particular, things get out of sync when the user makes frequent reversals of scrolling, for reasons I have not yet understood.

In analyzing the renderItems code, there seems to be room for optimization in the number of items rendered under both the scrolling up and scrolling down scenarios. Unfortunately, I have not been able to optimize these scenarios reliably - again, for reasons I do not yet understand. It almost seems as though there are synchronization issues between JS and the DOM - but that is probably just my imagination.

I will attack it again tomorrow ...

mherger
24 Dec 2010, 2:20 AM
Which of your changes does break things? Populating the all.elements list? The viewIndex?

It seems to be working fine for me so far. Haven't found any issues yet.

Vincent Rivoire
25 Dec 2010, 7:24 PM
Hi every one,

Beeing new to touch but long runner in Ext-js, I've came across a plugin for Ext: LiveGrid (http://www.sencha.com/forum/showthread.php?17791-Ext.ux.LiveGrid)

What are your opinions on porting/using it to touch?

norabora
26 Dec 2010, 6:02 PM
I'm glad I found this thread. I'm having hard time with List & massive data.
I hope Sencha Touch will be improved with your solutions.
Merry Christmas~

konki_vienna
27 Dec 2010, 3:12 AM
I have a question to the BufferedList-Code:

I am trying to implement it to my code and I' like to load the store-data from a server.

So I changed the baseConfig-Value 'data' from 'data: ListTestData' to 'data: myCustomStore.data.items'.
When I start the application, the store is empty and therefore the list as well. Then I load the data from the server and fill the store ('myCustomStore.add(...) ...).
But the list stays empty. Seems as there is no "refresh" functionality?

How can I fill the list/store dynamically?

MahlerFreak
27 Dec 2010, 8:27 AM
I've been working on a more comprehensive version of the buffered list (hopefully, group headers, variable height items, and some other things). I'll take a look at the refresh issues. I may not get to it for a couple of days, since I'm visiting family over the holidays.

MahlerFreak
27 Dec 2010, 9:06 AM
konki_vienna,

Try adding the following 3 lines of code at the bottom of the "refresh" listener, which you will find near the top of bufferedlist.js, and see if that fixes your problem - I suspect it will, but I haven't tested it:



refresh: function() {
if (!this.topProxy)
this.topProxy = this.getTargetEl().down('.ux-list-top-proxy');

if (!this.bottomProxy)
this.bottomProxy = this.getTargetEl().down('.ux-list-bottom-proxy');

if (!this.listContainer)
this.listContainer = this.getTargetEl().down('.ux-list-container');

// ADDED CODE - must render items on refresh, too.
this.topProxy.setHeight(0);
this.bottomProxy.setHeight(this.store.getCount() * this.itemHeight);
this.renderItems(this.scroller, {x:0, y:0});
},

konki_vienna
28 Dec 2010, 1:28 AM
Thank you for your hot-fix during your christmas-family-visit, but unfortunately adding the three lines of code throw an error:
'Uncaught TypeError: Cannot call method 'delay' of undefined'

23989

Apart from that I am looking very much forward to the result of your announced work on the more comprehensive version of the buffered lsit! :)
Enjoy your time with family!

MahlerFreak
31 Dec 2010, 4:07 PM
Attached is a substantially revised version of the BufferedList component, defined in the file BufferedList2.js. This is not yet complete, but does offer some enhancements and fixes:

- List now correctly updates when data is loaded into a store. Individual add, remove, or updates of records are not yet supported.
- Variable height list items are now allowed. You must still define a maxItemHeight parameter - see below.
- Items are now rendered in batches, rather than one or two at a time. This seems to deliver smoother scrolling on all platforms tested (iPad,iPod touch,Samsung Galaxy S).

Still to come: support for group headers, correct behavior on add/remove/update store records, ability to define a template for additional rendering just on visible items (e.g. lazy load of images), and trigger events for lazy loading of store data.

The attached zip file contains the BufferedList2.js file, as well as an example. You must edit BufferedList.html to point to your sencha touch library. Note that to use the BufferedList component, you must define maxItemHeight in your configuration object. The exact value of this parameter is not especially critical - as long as it is somewhat greater than the average height of your items, the component will work fine. The value used in the example, 85, is the size of a list item with three lines of text using the default Sencha list styling.

EDIT: I uploaded the wrong version, but it appears I fixed it before anyone downloaded.

MahlerFreak
1 Jan 2011, 8:59 AM
Here is another update, which fixes a bug that had bedeviled me occasionally from the beginning with BufferedList. It had to do with the cleanup task not being synchronized with the main execution thread, causing the list rendering to get out of sync and create duplicate items.

JavaScript is not a multi-threaded environment - except that it really is when executing functions on a delay. And there is no formal way to manage "thread collisions". Anyway, I digress. The attached BufferedList3.js can be substituted directly for the previous BufferedList2.js, with no other changes.

konki_vienna
3 Jan 2011, 12:51 AM
Once again: Impressive work, MahlerFreak!!! Thank you!

I have tested BufferedList3 on several devices, here is my report:
In general it seems that the list performs better on iOS-devices than on Android-devices, but I guess this is not new! :)

- iPad: works good,
- iPod-Touch (2nd generation, iOS 4): works good
- iPod-Touch (3rd generation, iOS 4): works good

- HTC Desire HD: when you continue scrolling before movement has stopped, the app freezes very often (regardless you change or keep scrolling-direction); when you reach the bottom of the screen/list the screen freezes very often again (and does not load/display new items)
- Sony Xperia mini: works good

Some general remarks:
- When you set 'indexBar: false', the length of the index bar does not reflect all list-items (as it was in previous versions), but only the relative length according to the currently loaded/displayed items. When the app loads/displays new items, the length of the index-bar gets smaller.
- On my Android-devices I see the browser-bar during loading of the bar, then the browser-bar fades-out and the app moves upwards to the very top of the screen, but the "new space" at the very bottom of the screen, does not get filled by the app, it stays empty. I am not sure whether or not this is an issue of the BufferedList, just wanted to mention it…

I am very much looking forward to the next versions of BufferedList!!!

ionik007
3 Jan 2011, 6:31 AM
Hello

I'm french and english is not my native language so i have mistake :D

res is a jsonp


var TPL = Ext.XTemplate.from('TPL');
var html = TPL .applyTemplate(res);
Ext.getCmp('mydiv').update(html);



Can anybody say to me how i can insert in my code for test ?

MahlerFreak
3 Jan 2011, 4:25 PM
konki,

I'm working on a fix for the issues you noted with scrolling on Android. Will post up tomorrow.

Scott

konki_vienna
4 Jan 2011, 1:51 AM
Hi Scott,
I am very much looking forward to your fix! I am curious if it can be fixed (as the freezing of the screen did only occur with one of two android smartphones)...

I've just tested the BufferedList3 on the Samsung Galaxy Tab: works smooth! :)

Best regards,
Konstantin

asinc
5 Jan 2011, 6:31 PM
Similar to other comments I've tested on iPhone 4 and an HTC Magic running Android 2.1. The iPhone scrolls quite nicely, it is just slightly off native performance and a little choppy but better than even the basic list performance in kitchen sink with less than 100 items. However, the Android device starts, stops, and stutters badly - it seemingly just can't handle the combination of rendering and JS execution.

Hoping that the performance of this can be moved forward as this is a great advance to the Sencha list scrolling issue!

jep
7 Jan 2011, 12:33 PM
A big thank you to all who have worked on this!

But would it be possible for you to include an example using this list? I just swapped out an existing (poorly performing) Ext.List with this one and it behaves quite oddly. Nothing but blank space below it. It's a pretty simple list, too:



Ext.regModel('Airports', {
fields:['airport_code','airport_name','country_code','latitude','longitude']
});

...

var airportsList = new Ext.ux.BufferedList({
store:new Ext.data.JsonStore({
model:'Airports',
getGroupString : function(record) {
return record.get('airport_name')[0];
},
sorters:'airport_name'
}),
data:[],
singleSelect:true,
grouped:true,
indexBar:true,
itemTpl:[
'<div class="airport">{airport_name}</div>'
]
});


Just trying to figure out if I'm approaching it wrong.

MahlerFreak
7 Jan 2011, 3:27 PM
Sorry all, I got called away for a couple of days. I am back working on this, and expect to post a new version and example tomorrow at latest.

MahlerFreak
8 Jan 2011, 3:26 PM
All,

Attached is the next version of BufferedList, as well as the example. This version has the following changes:

- General code cleanup.
- A new, faster, more reliable method of cleaning up old rendered items. This fixes some freezing/sticking issues after a scroll had ended.
- A new configuration parameter which can be used in list configuration, called "batchSize". This parameter sets the number of items which are rendered when the user is about to scroll off the top or bottom of the rendered item section. On my devices, there is little difference whether this parameter is set to 5 or 50 or any number in between. However, I am hoping that on some of the Android devices which aren't working very well, changing the value of this to lower numbers may help. Please let me know.
- A configuration parameter called "blockScrollSelect" is available. If this is set to true, item selection is blocked while the list is scrolling - similar to many native iOS applications. I find this behavior desirable when I just want to tap the list to stop scrolling, not select an item. This parameter is set to "true" in the provided example.

Some things to note on Android scrolling. Android does not currently provide for using accelerated graphics on any CSS transformations or animations; Android is generally far behind iOS in supporting GPU acceleration right now, despite the fact that most Android hardware has good GPU available. This accounts for basically all the difference in scrolling smoothness right now. Also, Android 2.2 has major improvements in Javascript performance over Android 2.1, and as 2.2 gets rolled out to all devices, this should help performance as well.

konki_vienna
10 Jan 2011, 5:33 AM
Hi MahlerFreak!
I'm glad, that you are back! :)

I tested your latest code again on several devices:
iOS:
- iPodTouch: good,
- iPad: good
Android:
- Motorola Milestone: ok (not scrolling smoothly)
- Samsung Galaxy S: very good
- Samsung Galaxy Tablet: good
- HTC Desire HD: better than BufferedList3, but still freezing sometimes during scrolling (but less often)

I will start and implement your code in my project soon, so I can test it more carefully.

Are you planing to add something/change the code or are you done with it?
Is the add-items-refresh issue working? (Had no chance to test it yet…)

Thank you for this great work, it is a crucial improvement for the Sencha-List-Stuff!!!

konki_vienna
10 Jan 2011, 6:18 AM
Will you add support for grouped headers? - Would be great!!!

MahlerFreak
10 Jan 2011, 6:56 AM
konki_vienna, and all,

My plan is to add group headers next, then support for add/remove/replace record. At that point, I will consider this project finished, as I have already spent a good deal more free time on it than expected :) At this point, I have no other ideas for improving interactivity and performance, other than a couple of minor tweaks currently implemented in my development version.

konki_vienna
10 Jan 2011, 7:00 AM
This sounds great! Thank you. Looking forward to it!!!

konki_vienna
13 Jan 2011, 6:14 AM
Hi MahlerFreak,
it seams as you cannot use XTemplate for the itemTpl. Is this correct? Is it hard to enable this functionallity?

MahlerFreak
13 Jan 2011, 1:12 PM
All,

I have posted the first complete release of BufferedList in the examples section:

http://www.sencha.com/forum/showthread.php?121225-High-Performance-Large-List-component-UxBufferedList

This release provides support for group headers, as well as add/remove/update functionality.

konki_vienna, at present your itemTpl must be specified as a string (which is compiled internally into an xTemplate). I may be able to enhance it to take an XTemplate later on.

konki_vienna
14 Jan 2011, 3:45 AM
Hi MahlerFreak,

once more: thank you for your effort and great work!

I have tested your code again and can just repeat myself:
on iOS everything works fine,
on fast android devices (good cpu/ram-settings) everything works fine, only on the telephone I use regularly and which is usually fast as well, the list stucks from time to time while scrolling. Perhaps this is only an HTC-Desire HD issue and not a general android issue?

I tried to implement your code from the "BufListGrpHdr"-example in my project and wanted to change the "getGroupString"-parameter from "return record.get('name')[0].toUpperCase();" to "return record.get('name').toUpperCase();", but I get an error: "UxBufList.js:619
Uncaught TypeError: Cannot read property 'index' of undefined".

Line 619 in your code is:
"groupStartIndex: function(groupId) {
return this.groupIndexMap[groupId.toLowerCase()].index;
},"

Perhaps you can give me a hint, what I have to do to make it work.
Thank you very much!!!

konki_vienna
14 Jan 2011, 5:58 AM
I do not manage to add/remove list-records to the list. Can you post a code snippet for that, too?

dingzhudingzhu
15 Jan 2011, 12:12 AM
My plan is to add group headers next, then support for add/remove/replace record. At that point, I will consider this project finished, as I have already spent a good deal more free time on it than expected At this point, I have no other ideas for improving interactivity and performance, other than a couple of minor tweaks currently implemented in my development version
I will start and implement your code in my project soon, (http://www.burberryhome.org) so I can test it more carefully. (http://www.burberryonline.net)
Attached is the next version of BufferedList, as well as the example. This version has the following changes:

- General code cleanup.
- A new, faster, more reliable method of cleaning up old rendered items. This fixes some freezing/sticking issues after a scroll had ended.
- A new configuration parameter which can be used in list configuration, called "batchSize". This parameter sets the number of items which are rendered when the user is about to scroll off the top or bottom of the rendered item section. On my devices, there is little difference whether this parameter is set to 5 or 50 or any number in between. However, I am hoping that on some of the Android devices which aren't working very well, changing the value of this to lower numbers may help. Please let me know.
- A configuration parameter called "blockScrollSelect" is available. If this is set to true, item selection is blocked while the list is scrolling - similar to many native iOS applications. I find this behavior desirable when I just want to tap the list to stop scrolling, not select an item. This parameter is set to "true" in the provided example.
Some things to note on Android scrolling. Android does not currently provide for using accelerated graphics on any CSS transformations or animations; Android is generally far behind iOS in supporting GPU acceleration right now, despite the fact that most Android hardware has good GPU available. This accounts for basically all the difference in scrolling smoothness right now. Also, Android 2.2 has major improvements in Javascript performance over Android 2.1, and as 2.2 gets rolled out to all devices, this should help performance as well.

J-zY
11 Mar 2011, 5:08 AM
Hi Quick question!

I have around 3000+ items in my list and only a small part of this is viewed. If I scroll any further my list becomes all white. Did I forget to set something?

Thanks

hotdp
19 Jul 2011, 11:31 PM
Hello
I have problems with the performance using this list:

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?

Martin1982
4 Oct 2011, 1:33 AM
Hello
I have problems with the performance using this list:

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?

This is exactly the same issue I'm battling with now, any suggestions are more than welcome...

Martin1982
6 Oct 2011, 12:15 PM
Ok I had my eureka! moment, the situation described above is better solved with a Dataview. Lists seem to take up loads of resources.

The even beter part is that if you are using the listpaging plugin the only thing you need to change is the event in the init() function and it will work as well.

frogt
4 Jan 2012, 7:13 AM
Big thanks to those who contributed to Ext.ux.BufferedList - works perfectly for my (smallish) list of 500 items.

pmahoney
2 Mar 2014, 5:15 AM
Basically, has it been ported to Touch 2.x yet?