PDA

View Full Version : High Performance Large List component - UxBufferedList



MahlerFreak
13 Jan 2011, 1:02 PM
This is the first functionality-complete release of the list component I and others have been working on in this thread:

http://www.sencha.com/forum/showthread.php?119185-List-performance-consolidation-thread/

Ext.ux.BufferedList is designed to be used in place of the standard Ext.List component, and supports essentially all Ext.List functions and configuration parameters, while providing the following enhancements and changes:

- Adequate scrolling performance with large data sets. The attached examples use a data array of about 1400 items, while still providing similar scroll performance to Ext.List. This is accomplished by rendering a "sliding window" of list items based on current scroll position, rather than rendering all list items.
- Independent support for indexBar and group headers. By setting the standard list config parameter "grouped" to true, and the extension config parameter useGroupHeaders to false, you can use an index bar without having group headers, as some native iOS applications do. Obviously, setting useGroupHeaders to true gives you standard group headers.
- The configuration parameter "blockScrollSelect" is provided. If set to true, this prevents item selection while the list is still scrolling, so you can tap the list to stop scroll without invoking a selection - again, similar to native iOS. See the attached examples.

The only non-intuitive configuration is a parameter called "maxItemHeight". This is set at 85 pixels by default. If you have a significant number of items in the list which will be greater than this height, you should increase the value at least to the 90th percentile or so item height, to avoid potential problems with long scrolls to the top of the list. Also, itemTpl should be specified only as a string, not as an XTemplate, although this restriction may be removed soon.

I've licensed this work under either GPL or MIT, which I believe should allow for any forseeable reuse.

Please let me know of any bugs, enhancements, etc.

The attached file contains both the UxBufList.js file, and three examples demonstrating regular, indexBar, and grouped with headers lists. You will need to edit the html files to point to your sencha touch library.

Edit: I have joined the 21st Century and will not be posting updates in zip file format any longer. The latest will always be found here:

https://github.com/Lioarlan/UxBufList-Sench-Touch-Extension

profunctional
13 Jan 2011, 1:55 PM
Hi, thanks for this. In the BufListGrpHdr example, if I set the indexBar: false, it breaks. How do we run the example without the index bar?

MahlerFreak
13 Jan 2011, 2:26 PM
BufList.html is set up to work without groups or index bar.

Edit: did you want group headers but not index bar? I honestly didn't think of that, but it could probably be done pretty easily ...

SunboX
14 Jan 2011, 4:51 AM
Could this be used to build a " High Performance Large PICKER component" too? If so, where should i start?

MahlerFreak
14 Jan 2011, 1:57 PM
I suppose it could be - what does the application look like? I have minimal time to enhance this any further at present, but I might be able to make general suggestions ...

SunboX
15 Jan 2011, 1:56 AM
I did have a Picker containing a dataset of 1000 items. It shows 3 items per page. If i tap on the belonging select field, the picker (the scroller inside the picker) takes some seconds to render and if i flip (scroll) through the "pages" itīs very slow. :/
I have a seconds Picker with (only) 60 items, its a lot faster, lets say super fast. That drives me crazy, canīt finish my app. That picker with 1000 items isnīt usable.

lchurch
20 Jan 2011, 1:49 PM
coming from other mobile environments I would suggest two things:
1. drop list or pickers don't work well when the list is > 50 items
2. We use a list screen where the entire screen is a list of items to choose from.

Ideally you implement a "progressive" search on the list, so that if I type in "S", the list jumps to the first "S" in the list. Progressive means that if the user types "Su" the list jumps to the first entry with "Su".

This is all hypothetical in this this environment as I' a newbie with JS and these devices. But I've been building tablet and PDA stuff since 1992.

kortovos
31 Jan 2011, 7:22 AM
Looks very nice, but I have some suggestions.

1. You cann't use the groups without an indexbar.
2. You cannot have getGroupString functions which return more than one character
3. Disclosure buttons would be nice (yes I know it isn't to difficult to add something like this myself, but it would be nice to have it in the plugin)

I will also try to add these things myself and will post them here if I get it working.

benjamin.jaton
4 Feb 2011, 11:04 AM
I really appreciate all the effort put on this subject. The BufferedList implementation works great.

May be you could help me with an issue I have with this simple piece of code :


WP.components.PeopleList = new Ext.ux.BufferedList({ //new Ext.List({
fullscreen: true,
itemTpl: '{name}<br/>{title}',
maxItemHeight: 75, // must specify
blockScrollSelect: true,
batchSize: 10,
store: new Ext.data.Store({ model: 'Contact' })
});

WP.components.PeopleList.bindStore(WP.stores.People);

The initial render works fine but when I trigger a read() on my store, the BufferedList doesn't render any item.

After calling the read() method on the associated store I can see that some methods are triggered on my BufferedList :
- refresh
- collectData ( records contain actual data )
- renderOnScroll
- replaceItemList
- getNode
- isItemRendered
- buildItemHtml ( the HTML code produced for the records seems to be OK )
So everything seems fine..

If I switch to the Ext.List implementation, then the new items are getting rendered fine.

I also have some problem trying to add a dockedItem to the list / removing the fullscreen:true parameter ( no display )

I am still learning the framework and would appreciate any advice/comment on that.

benjamin.jaton
4 Feb 2011, 11:32 AM
onItemDisclosure support can be achieve by replacing some code in UxBufList.js :


// line to replace (35):
//this.itemTplDelayed = new Ext.XTemplate('<div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div></div>').compile();

// onItemDisclosure support
this.itemTplDelayed = '<tpl for="."><div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div>';
if (this.onItemDisclosure) {
this.itemTplDelayed += '<div class="x-list-disclosure"></div>';
}
this.itemTplDelayed += '</div></tpl>';
this.itemTplDelayed = new Ext.XTemplate(this.itemTplDelayed).compile();
// end onItemDisclosure support

It is pretty much a copy/paste of the List.js code, but I thought it could help some ppl.

MahlerFreak
7 Feb 2011, 6:11 PM
Hi Benjamin,

Sorry I didn't see this sooner. I will take a look at the failure to render on read().

And thank you for the item disclosure fix - I'll incorporate that in my next "release".

Scott

benjamin.jaton
9 Feb 2011, 4:55 PM
Actually the dockedItems are fine :) I just made a mistake in my code.

That would be great if you could have a look at the read() items update.
Here is a small testcase for that :


Ext.reg('mylist', Ext.ux.BufferedList);

Ext.setup({
onReady: function(){
Ext.regModel('Contact', {
fields: ['firstName', 'lastName']
});

var data = [
{firstName:'John', lastName:'Lenon'},
{firstName:'Frank', lastName:'Zappa'}
];

var store = new Ext.data.JsonStore({
model: 'Contact',
data: data
});

new Ext.TabPanel({
fullscreen: true,
items: [{
xtype: 'panel',
title: 'MyPanel',
dockedItems: [{
xtype: 'button',
text: 'filter list',
handler: function(){
Ext.getCmp('mylist').store.filter('firstName', 'John');
}
}]
}, {
id: 'mylist',
title: 'MyList',
xtype: 'mylist',
itemTpl: '{firstName} {lastName}',
store: store
}]
});
}
});

Steps :
- Go to MyList, check that there are 2 items
- Go back to MyPanel and click on the button ( the list should have 1 item now )
- Go back to MyList, there is nothing anymore

Note : replacing the first line Ext.reg('mylist', Ext.ux.BufferedList);
by Ext.reg('mylist', Ext.List);
makes it work

dontbugme
10 Feb 2011, 2:17 PM
First of great job on the list, it works very well apart from one issue.

After rendering the list once, when I reload the store the list appears blank - any idea how to solve this?

Basically what I am doing is, when showing the bufferedList the store is loading the data from a proxy with a letter as an argument in order to only load entries starting with this specific letter. Now this works on the first try but on the second I get the error "Uncaught TypeError: Cannot call method 'call' of undefined"

MahlerFreak
10 Feb 2011, 5:47 PM
Benjamin,

Thanks for the example, I have some time put aside to work on this tomorrow.

Scott

benjamin.jaton
10 Feb 2011, 9:39 PM
Hi Scott,

no problem, if you need any early testing let me know.

MahlerFreak
15 Feb 2011, 12:55 PM
Benjamin,

Attached is a new version of UxBufList, with a fix for your update bug.

Edit: fix is now in the first post of the thread, along with subsequent fixes.

Regards,

Scott

benjamin.jaton
16 Feb 2011, 10:47 AM
Hi Scott,

First, thank you so much for taking the time to do this, it is much appreciated.

So about the newer version of the code, it does fix the test case I sent. However a read() call on the store will still empty the list out. So I created a new testcase using a json store which is closer from what the original problem I had :


Ext.reg('mylist', Ext.ux.BufferedList); // would work with Ext.List

Ext.setup({
onReady: function(){
Ext.regModel('Contact', {
fields: ['firstName', 'lastName']
});

var store = new Ext.data.Store({
model: 'Contact',
proxy: {
type: 'ajax',
url : 'data.js',
reader: {
type: 'json'
}
},
data : [
{firstName:'John', lastName:'Lenon'},
{firstName:'Frank', lastName:'Zappa'},
{firstName:'Led', lastName:'Zeppelin'}
]
});

new Ext.TabPanel({
fullscreen: true,
items: [{
xtype: 'panel',
title: 'MyPanel',
dockedItems: [{
xtype: 'button',
text: 'read new data',
handler: function(){
Ext.getCmp('mylist').store.read();
}
}]
}, {
id: 'mylist',
title: 'MyList',
xtype: 'mylist',
itemTpl: '{firstName} {lastName}',
store: store
}]
});
}
});

the testcase requires a data.js available :


[
{"firstName":"Charlie", "lastName":"Chaplin"},
{"firstName":"Sarah", "lastName":"Bernhardt"}
]

From what I can see, the refresh() method in UxBufList gets called with this.onUpdate().

MahlerFreak
18 Feb 2011, 12:35 PM
Benjamin,

Thanks again for a clear, simple example. The attached fix fixes both of your issues, and hopefully a broad category of others as well. I was trying to be excessively clever in terms of keeping the user's scroll position when data changes occur; this really wasn't very sensible under the kind of wholesale data change scenarios you are doing.

Edit: this fix is now in the file attached to the first post in the thread, UxBufList014.zip.

Regards,

Scott

benjamin.jaton
18 Feb 2011, 3:55 PM
Hi Scott,

Your fixes work great. I am now using it in one of my app :)
Thank you so much!

- Playing with it a bit I found an interesting behavior when you change the active Component while scrolling in the list :
1) Go to 'MyList' tab
2) scroll hard and quickly tap on the 'MyPanel' tab
3) wait a second (for the scrolling to complete)
4) go back to the list => no item rendered

Note : If you scroll after 4), the items show up

Here is a simple testcase as usual :


Ext.reg('mylist', Ext.ux.BufferedList);

var data = []
for (var i=0; i<600; i++) data.push({firstName: 'John'+i, lastName: 'Wayne'})

Ext.setup({
onReady: function(){
Ext.regModel('Contact', {
fields: ['firstName', 'lastName']
});

var store = new Ext.data.Store({
model: 'Contact',
data : data
});

new Ext.TabPanel({
layout:'fit',
fullscreen: true,
scroll:'vertical',
items: [{
title: 'MyPanel',
xtype: 'panel'
},{
title: 'MyList',
fullscreen: true,
xtype: 'mylist',
itemTpl: '{firstName} {lastName}',
store: store
}]
});
}
});

What I did in my app was enabling/disabling the scroller when I switch panels. But I guess there is a better solution.

Do you think that this component has any chance to be added to the framework ?
I'd love to have it included since it is way more efficient than the default implementation !

MahlerFreak
18 Feb 2011, 5:28 PM
Benjamin,

I'll take a look at the component switching issue. I suspect it will be simple to fix. Thanks again for making a clear test case. I especially like the John Wayne data theme - perhaps you can come up with a SASS/CSS3 visual theme to match? :)

I wouldn't think this is likely to end up in the base framework. The way I'm rendering things here is outside the paradigm of the base framework components, and that has some consequences. For instance, the getNode function will always return the DOM node for a record index associated with a visible node. But it can return null for an index of a node outside the current display "window". For many applications, this has no consequence, but for some it might. There are probably some other little "gotchas" like the latest one you found. So this extension is definitely useful for applications that must perform with large lists, but it may not meet the total criteria for consistency that the Sencha folks rightly ask of all their product. As a software architect by background, I get that.

I'd also guess that the percentage of people developing on Touch that must handle large lists is relatively small. And if I were a Sencha developer/architect, I'd be hoping that advances in hardware and OS, combined with optimizations and tweaks in the Sencha scrolling code itself, will eventually obviate the need for specialized components like UxBufList.

Meanwhile, those of use who need it can use components like UxBufList where they work for us. This is the great advantage provided a large community developing and sharing extensions for each other.

Regards,

Scott

MahlerFreak
23 Feb 2011, 10:47 AM
Benjamin,

The latest bug is fixed. The file is attached in my original post - I discovered that if you edit posts under IE, of all things, you get the advanced editor and can change the attached files. All others reading this thread should get the fix from the original post.

Edit: My bad (at least partly). It turns out that if you click "edit post", then Go Advanced, you don't get the advanced editor. But if you double-click on edit post, it goes straight to advanced editor on all platforms.

Scott

chrisfarms
24 Feb 2011, 8:55 AM
This is excellent, thanks.

I was disappointed to find that this method wasn't used in the standard List control, I hope this makes it into core, I'm sure they will need it if they intend on making some kind of grid control.

I did struggle a little at first as I did not have the "fullscreen: true" set, and have no idea why that was required .... guess I should learn about layouts next :)

Have you thought about using github/bitbucket as some others do to for their extensions, could make updates a bit easier than replacing a zip. Thanks again anyway sure this will come in handy.

MahlerFreak
24 Feb 2011, 11:26 AM
Have you thought about using github/bitbucket as some others do to for their extensions, could make updates a bit easier than replacing a zip. Thanks again anyway sure this will come in handy.

Yes, I should set something up. It's been 10 years since I was an active coder, and the whole world of source code control and collaboration is radically different now :)

Shale
2 Mar 2011, 9:05 AM
Scott,
This is great stuff! Thanks very much for you work. I only just found this and the List performance consolidation thread and it looks like exactly what I need. One thing is the current UxBufList014.zip attached to the first post of this thread doesn't contain albumdata.js. I found a copy in the BuffererdList4.zip from the other thread, but you might want to make a new UxBufList zip with everything in it.

thanks again!

Shale
2 Mar 2011, 2:08 PM
Scott, there's one other thing I wanted to ask. In the other thread you said the following


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.

That's the exact use case I'm interested in. Does BufferedList meet those requirements for you?

I'm still getting up to speed with sencha touch, but it seems like Store doesn't natively support incrementally loading rows as needed and forgetting old ones. Also, I've been toying with different types of lists, both very large lists and dynamically changing lists, which would be difficult for my HTTP server to expose as a list of known size indexed 0 to N. Instead I'd like to support both lists that have no known total length and ordered lists that use javascript objects as keys instead of integers.

I want to be clear: this isn't a feature request aimed at you! Your BufferList is an eye-opening example of extending the library's List widget for me, and I'd really appreciate your thoughts on what I said above. Also, has anyone else tried anything to support different types of lists or anything similar?

thanks

gcallaghan
2 Mar 2011, 2:43 PM
First off, this is soooo helpful. It saved me an unbelievable amount of time. Thank you so much for sharing.

for my particular need I have a list with groups but no indexBar. I was able to use this with headers and without an index bar by making the following changes.



...
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 = 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 (!!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;
}

},
...

gcallaghan
2 Mar 2011, 2:46 PM
I also had a case wher I had special characters in my heading, namely &.

This caused issues with the groupIndexMap. I was able to workaround this with the following changes.



// @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 (!!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;
},

MahlerFreak
2 Mar 2011, 4:01 PM
Scott,
This is great stuff! Thanks very much for you work. I only just found this and the List performance consolidation thread and it looks like exactly what I need. One thing is the current UxBufList014.zip attached to the first post of this thread doesn't contain albumdata.js. I found a copy in the BuffererdList4.zip from the other thread, but you might want to make a new UxBufList zip with everything in it.

thanks again!

Thanks for catching that! I've updated the zip file in the first thread post.



That's the exact use case I'm interested in. Does BufferedList meet those requirements for you?


Not entirely as it stands, because as you note you don't really want to download all 100K tracks in one lump. I'm experimenting on my end with some modifications/additions to dynamically load/unload data as part of the dynamic rendering process. These modifications do require that the server be able to report what the total number of items in a given list is, and to be able to return subsets of that data based on indexes (see the documentation on AjaxProxy for how this would be used). This also implies that I do not do any searching/sorting of the JSON data on the client - it's all done by the server, and the results of said searches/sorts are cached in the server against a unique id supplied by the client. So when the user performs some action which changes the current contents/order of a list, I first signal the server to perform the search/sort and cache the results, then make repeated queries against that cache to return subsets of the data as needed for rendering. Does that make sense? Because I've written my own server in C++, I can have it do exactly what is needed to support the client - you may not have that luxury.

Can you tell me anything about what you're working on? You can PM me if you want ...

MahlerFreak
2 Mar 2011, 4:08 PM
First off, this is soooo helpful. It saved me an unbelievable amount of time. Thank you so much for sharing.

for my particular need I have a list with groups but no indexBar. I was able to use this with headers and without an index bar by making the following changes.



Thank you gcallahan for these enhancements, and the ones below. I think someone else earlier in the thread asked for exactly what you needed: groups without index bar. I'll put these additions in the next "release".

Glad you found this useful.

Scott

norabora
3 Mar 2011, 1:16 AM
Thank you MichaelFrank for this awesome BufferedList.
and also thank you, gcallaghan.
your fix makes getGroupString possible to return more than a char.

norabora
4 Mar 2011, 1:06 AM
I found a bug.
If there is only one item. selectedCls is not applied.
It seems that bottomItemRendered is 0, so getNode returns null.
I changed source a little bit like below and it is solved.

isItemRendered: function(index) {
// Trivial check after first render
if (this.store.getCount() == 1) return true; // modified. because if count is 1, bottomItemRendered is 0.
return this.bottomItemRendered > 0 ?
index >= this.topItemRendered && index <= this.bottomItemRendered : false;
},

I'm not sure this fix is safe though...

kgilb
8 Mar 2011, 7:01 AM
Another request to put this on github.

I've made some of my own modifications to the source and being able to have a branch that I can merge with the master changes would be fantastic.

For instance i've added support for my own custom CSS class in the item template:



// custom item class to be added to each item
itemCls: '',

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


And then I wanted to add the item disclosure support, but I wanted to be able to do it after the component was already defined so I refactored the initComponent to call rebuild template so you can change the template at run time (for instance if you add/remove an onItemDisclosure function).



initComponent: function () {
this.rebuildTemplate();
// ...
},

rebuildTemplate: function(){
// onItemDisclosure support
this.itemTplDelayed = '<tpl for="."><div class="x-list-item ' + this.itemCls + '"><div class="x-list-item-body">' + this.itemTpl + '</div>';
if (this.onItemDisclosure) {
this.itemTplDelayed += '<div class="x-list-disclosure"></div>';
}
this.itemTplDelayed += '</div></tpl>';
this.itemTplDelayed = new Ext.XTemplate(this.itemTplDelayed).compile();

},


Now I'm in the process of merging build 14 of the source back into my changes. It's not hard to do, but github would make this much easier.

Thanks for all the work on this!

MahlerFreak
8 Mar 2011, 2:23 PM
Ok, by popular demand :) I've created a GitHub repository for the component. You can find it here:

https://github.com/Lioarlan/UxBufList-Sench-Touch-Extension

Right now, it has exactly the same code as UxBufList014.zip in the first thread post. I'll be pushing some additional fixes in the next couple of days.

This is my first project on GitHub, and indeed it's the first time I've used a SCCS in more than 10 years, so let me know if there's anything I should be doing different to configure it, etc.

MahlerFreak
8 Mar 2011, 4:15 PM
The enhancements by gcallahan and a fix for the bug found by norabora have been posted to github as version 0.15.

norabora
9 Mar 2011, 6:38 PM
New bug in 0.15.
if group header is unicode. pinHeader show escaped string like %U3131.
If I remove escape in UxBufList.js, it is ok.

norabora
9 Mar 2011, 9:24 PM
Another bug.
If I select some items and do filter which includes the selected item,
addCls of null error occured.

To test add below code after loadData,

list.getSelectionModel().select(0);
list.store.filter('album', 'A');

I followed the stacktrace.

filter() -> AbstractStoreSelectionModel.refresh() ->
if toBeSelected.length > 0, doSelect(toBeSelected) ->
... -> onItemSelected -> getNode ->
isItemRendered returns null because this.all.elements.length is 0 at that point.

I tried to override onItemSelect with null check just like onItemDeselect,
the error is not happend but selectedCls is not applied.

I'll post if I find better solution.

norabora
15 Mar 2011, 8:50 PM
When I use bufferedList with group header.

loadData -> scroll up a little bit -> scroll down so scroll pos will be 0
-> PinHeader and group header both shown.

Usually it doesn't matter but I added shadow css to group header, so it look a bit different.

To fix this, I changed the code in UpdateListHeader()

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 if (groupTop < scrollPos) { // modified. original code didn't have if condition.
this.updateHeaderText(headerNode.innerHTML);
if ( this.transformedHeader ) {
this.header.setStyle('-webkit-transform', null);
this.transformedHeader = false;
}
}

I'm not sure this fix is safe. Will anyone verify this, please?

norabora
16 Mar 2011, 1:05 AM
I tried to make a page with BufferedList and 'More' button.

When I click 'More' button, list.store.loadData(array, true) will called so array will be added to the old list.

but if I test it like 'click more' -> scroll down to end -> 'click more' -> scroll down to end' ...

The buffered list starts to be blank after loadData. and it shows up again when scroll starts.

specially the items count exceeds maxItemlength.

I tried to debug but it is hard... Any help will be appreciated.

hitman01
18 Mar 2011, 10:55 AM
How can use Ext.XTemplate.from('contentListTemplate') with BufferedList?

app.views.newList = Ext.extend(Ext.ux.BufferedList, {
cleanupBoundary: 50,
minimumItems: 10,
maxItemHeight: 75,
blockScrollSelect: true,
batchSize: 10,
itemTpl: Ext.XTemplate.from('contentListTemplate'),
store: app.stores.contacts,

onItemDisclosure: function (record) {
},
});

norabora
19 Mar 2011, 3:37 AM
I never tried Ext.XTemplate.from() but I saw at the first post in this thread,

'...itemTpl should be specified only as a string, not as an XTemplate...'

norabora
20 Mar 2011, 4:31 PM
I answered to my 'More button' question by myself.
I added this.renderOnScroll() just below this.renderOnScroll(0) in refresh() and it fixed.
Because it shows up again when scroll starts again.

This seems like a little bit of abundant call but it works as expected so far.
(except the offset is not adjusted if there are group headers.)

norabora
20 Mar 2011, 5:32 PM
To keep selection during scrolling,
scroll stop --> itemCleanup() --> updateItemList() --> keep previous selection.
but sometimes this doesn't work.

Because sometimes this.all.elements.length is less than cleanupBoundary so updateItemList() is not called in itemCleanup.

To fix this temporarily, I called save & restore selection in itemCleanup (outside of if condition),
but I'm not sure I'm on the right track...:-/

eMedTools
21 Mar 2011, 11:00 AM
Hi everyone.

I am new to Sencha Touch, and found your High Performance Large List - wow - what a find!

- I have built a prototype app with Jquery Mobile, which searches nicely, but is waaaaaay slow when working with a list of 1,000 entries, so I am trying Sencha Touch to see if that will work.

It does nearly everything I need it to, but how does one create a search function for the list?

Any assistance would be greatly appreciated.

Thanks

norabora
21 Mar 2011, 5:28 PM
To keep selection during scrolling,
scroll stop --> itemCleanup() --> updateItemList() --> keep previous selection.
but sometimes this doesn't work.

Because sometimes this.all.elements.length is less than cleanupBoundary so updateItemList() is not called in itemCleanup.

To fix this temporarily, I called save & restore selection in itemCleanup (outside of if condition),
but I'm not sure I'm on the right track...:-/

I found better solution.
In renderOnScroll, calling 'save & restore selection' after replaceItemList, appendItems, insertItems.
Then, after every dynamic add/remove, reselect previous selection.

Bucs
24 Mar 2011, 1:15 PM
Great ux, performance is wonderful!

Everything is working great, list items are showing, as well as headers, but for some reason the indexBar will not show up, any ideas as to why not?



{
hidden: true,
id: 'listMfg',
xtype: 'bufferedlist',
grouped: true, // optional
indexBar: true, // optional
useGroupHeaders: true, // no group headers
maxItemHeight: 30, // must specify
blockScrollSelect: true,
batchSize: 20,
loadingText: null,
store: new Ext.data.Store(
{
storeId: 'listMenuStore',
model: 'MenuItem',
proxy: {
url: 'GetList',
type: 'ajax',
reader: {
type: 'json'
}
},

getGroupString: function (record) {
return record.get('name')[0].toUpperCase();
},

listeners: {
scope: this,
read: function (cmp, root, recs) {
Ext.getCmp('listMfg').setHeight(300).show();
this.query('LoadingPanel')[0].hide();
}
}
}
),
itemTpl: '{name}'
},



Thanks!

Bucs
24 Mar 2011, 4:17 PM
Nevermind, I had not included the indexBar in the application.scss file so it didn't generate the necessary CSS classes. All is great now...thanks again for this highly useful extension.

tinyfactory
22 Apr 2011, 11:07 AM
This just made a project I was working on happen! Thanks so much for developing it. Any plans to implement XTemplate in the near future?

Where can I donate to keep this project going?

Keep it up!

Alexander Rolek

vinalys
22 Apr 2011, 9:52 PM
I am really new to Sencha touch so I might make some mis-configuration here. I believe I found a few bugs which I traced by using some logs. All of them are found with the options grouped, useGroupHeader and indexBar to true:

If the items that are sorted contain caps and some others not, this is possible that there are some issues on ordering. For instance I had some items 'a' and 'L'. When displaying the list, the first row shown will be the one with L, I believe because its ASCII code comes before a.

The second issue I am facing is with non alpha characters. I add a '2', nothing will be displayed except the indexBar. This is because '2' is not contained int that indexBar. Of course the listPrefix does not help but this could be addressed by defining inside the array "letters" every characters that my list does contain as a start.

vinalys
22 Apr 2011, 10:00 PM
I add my method to find the first issue with problems of sorting:

I added the red lines in function initGroupIndexMap:


// 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;
console.log("add ix: "+i+", closest: "+key+", prev: "+prevGroup);
}
if ( !firstKey ) {
firstKey = key;
console.log("1st key: "+firstKey);
}
}

Which gives this output if my list contains L a M:

add ix: 0, closest: l, prev: l
1st key: l
add ix: 200, closest: m, prev: m
add ix: 400, closest: a, prev: a


For the second issue I am quite sure it comes from the remaining part of the same function that browse all items available in the indexBar including only alpha characters by default.

realjax
3 May 2011, 4:24 AM
Thanks. Nice work.

In order to get the buffered list to work with an itemTpl that is an array or an instance of Ext.XTemplate (which may contain custom rendering functions) the source needs to be adjusted so the initComponent reads like this:



initComponent: function() {

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 = '<div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div></div>';
this.itemTplDelayed = new Ext.XTemplate(this.itemTplDelayed, memberFnsCombo).compile();


Ext.ux.BufferedList.superclass.initComponent.call(this);

//.. etcetera...



I'm not sure why your replaced the orginal's Ext.List tpl code:

<div class="x-list-item ' + this.itemCls + '"><div class="x-list-item-body">' + this.itemTpl + '</div>
with

<div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div></div>';
?
Am I not allowed to add custom CSS rules to the items in bufferedList?

danmux
9 May 2011, 11:20 AM
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?

danmux
9 May 2011, 5:19 PM
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.

Ghostface
10 May 2011, 8:37 AM
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).

danmux
10 May 2011, 8:44 AM
This is the change I made that fixes that issue....

https://github.com/danmux/UxBufList-Sench-Touch-Extension/commit/a3175b6bf8df6c5de32016ab7af8c7cb12bf9ad6

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>

Ghostface
10 May 2011, 9:12 AM
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.

hotdp
21 Jul 2011, 3:49 AM
Hello
I posted in the old thread so I will also post my problem here.

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?

jlscott3
22 Jul 2011, 1:08 PM
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:


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:

27110

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.

Surykat
25 Jul 2011, 2:17 AM
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:

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.

Nikkelmann
28 Jul 2011, 12:54 AM
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:

{
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 (http://dev.sencha.com/deploy/touch/docs/source/DataView1.html#cfg-Ext.DataView-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:


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.


/*
*
* 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.

headkit
22 Aug 2011, 4:55 AM
*subscribe*

Dannydekr
23 Aug 2011, 4:01 AM
Since my list is getting bigger and bigger, I forced myself to use this great extension. Seems to work very good, I only have a problem with the onItemdisclosure. It simply won't work. It will not go to the detailspane at all, I can't seem to figure out why this is the case:



Ext.ns('ShotjesApp'); Ext.setup({
onReady : function() {
Ext.regModel('Contact', {
fields: ['Naam', 'Inhoud']
});


var baseConfig = {
itemTpl: '<div><p><strong>{Naam}</strong></p></div>',
grouped: true, // optional
indexBar: true, // optional
maxItemHeight: 75, // must specify
blockScrollSelect: true,
batchSize: 10, // not required, may require tweaking for some platforms

store: new Ext.data.Store({
model: 'Contact',
sorters: 'Naam',
getGroupString : function(record) {
return record.get('Naam')[0];
},

// data: ListTestData
})

};


ShotjesApp.listPanel = new Ext.ux.BufferedList(Ext.apply(baseConfig, {
fullscreen: true,
id: 'listpanel',
onItemDisclosure: function(record) {
var naam = record.data.Naam;
ShotjesApp.detailPanel.update(record.data);
ShotjesApp.Viewport.setActiveItem(ShotjesApp.detailPanel, {type:'fade'});
ShotjesApp.detailPanel.dockedItems.items[0].setTitle(naam);
}
}));

ShotjesApp.detailPanel = new Ext.Panel({
id: 'detailpanel',
tpl: '{Inhoud}',
layout: 'fit',
scroll: false,
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [{
text: 'terug',
ui: 'back',
handler: function() {
ShotjesApp.Viewport.setActiveItem('listpanel', {type:'fade'});
}
}]
}],
});

ShotjesApp.Viewport = new Ext.Panel ({
fullscreen: true,
layout: 'fit',
items: [ShotjesApp.listPanel, ShotjesApp.detailPanel]
});

// test to see if data load is handled correctly
ShotjesApp.listPanel.store.loadData(ListTestData);
}
});

Surykat
23 Aug 2011, 4:12 AM
You need to add a support (code below) for this event in initComponent() method of uxBuffList.



// onItemDisclosure support
this.itemTplDelayed = '<tpl for="."><div class="x-list-item"><div class="x-list-item-body">' + this.itemTpl + '</div>';
if (this.onItemDisclosure) {
this.itemTplDelayed += '<div class="x-list-disclosure"></div>';
}
this.itemTplDelayed += '</div></tpl>';
this.itemTplDelayed = new Ext.XTemplate(this.itemTplDelayed).compile();
// end onItemDisclosure support

Dannydekr
23 Aug 2011, 4:15 AM
Edit: went on another route :)

Dannydekr
23 Aug 2011, 4:53 AM
Edit: Fixed the problem that it wouldn't show (just needed to moved the main JS file a few rows). Also fixed the problem it wouldn't show any items after 50 items. Needed to update the main JS file with some code I found here.

Now the only big problem remains. And that is the onitemdisclosure that just would not work at all, it is working when I make it the standard ext.List. And yes, the onItemDisclousre code is in the uxBufferlist.js file.

I can press all I want, it just won't go to the details pane..

This is my code:

(the iOS stuff at the top is to reduce the loadingtime/white flash problem)


Ext.ns('ShotjesApp');Ext.setup({
onReady: function() {
ShotjesApp.Main.init();
}
});
if (Ext.is.iOS && Ext.is.Phone) {
Ext.Viewport.init = function(fn, scope) {
var me = this,
stretchSize = Math.max(window.innerHeight, window.innerWidth) * 2,
body = Ext.getBody();
me.updateOrientation();
this.initialHeight = window.innerHeight;
this.initialOrientation = this.orientation;
body.setHeight(stretchSize);
Ext.defer(function() {
me.scrollToTop();
if (fn) {
fn.apply(scope || window);
}
me.updateBodySize();
}, 50);
};
}


ShotjesApp.listPanel = new Ext.ux.BufferedList ({
store: ListStore,
id: 'listpanel',
layout: 'fit',

grouped: true, // optional
indexBar: true, // optional
maxItemHeight: 75, // must specify
blockScrollSelect: true,
batchSize: 10, // not required, may require tweaking for some platforms


itemTpl: '<div><p><strong>{Naam}</strong></p></div>',

onItemDisclosure: function(record) {
var naam = record.data.Naam;
ShotjesApp.detailPanel.update(record.data);
ShotjesApp.listContainer.setActiveItem(ShotjesApp.detailPanel, {type:'fade'});
ShotjesApp.detailPanel.dockedItems.items[0].setTitle(naam);

}
});
ShotjesApp.detailPanel = new Ext.Panel({
id: 'detailpanel',
tpl: '{Inhoud}',
layout: 'fit',
scroll: false,
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [{
text: 'terug',
ui: 'back',
handler: function() {
ShotjesApp.listContainer.setActiveItem('listpanel', {type:'fade'});
}
}]

}],
});
ShotjesApp.listContainer = new Ext.Panel ({
layout: 'card',
items: [ShotjesApp.listPanel],

})
ShotjesApp.mainToolbar = new Ext.TabPanel ({
flex: 1,
xtype: 'tabpanel',
tabBar: {
dock: 'bottom',
layout: {
pack: 'center'
}
},
ui: 'dark',
cardSwitchAnimation: {
type: 'fade',
cover: true
},
scroll: 'vertical',
items: [{
title: 'Shotjes',
iconCls: 'home',
cls: 'card',
layout: 'fit',
items: ShotjesApp.listContainer
}, {
title: 'Informatie',
iconCls: 'bookmarks',
layout: 'fit',
cls: 'card',
html: 'Over deze applicatie',

}]
})
ShotjesApp.Main = {
init : function() {
new Ext.Panel({
fullscreen: true,
layout: {
type: 'fit',
},
items: [ShotjesApp.mainToolbar]
});
}
};




The UxBufList.JS I use came from the previous page and is provided by Nikkelmann. It contains all the bug fixes and contains the ondisclosureitem code.


EDIT: Seemed that I fixed the onDisclosureItem problem by adding this code in the index.js. I forgot I removed the disclosure button and changed my ext.list so the whole row would be clickable :)

Ext.override(Ext.List, {
afterRender: function(){
Ext.List.superclass.afterRender.call(this);
if (this.onItemDisclosure) {
this.mon(this.getTargetEl(), 'singletap', this.handleItemDisclosure, this, {
delegate: '.x-list-item-body'
});
}

}
});


Hopefully this will be it.

Edit:

Also, the code for the onItemDisclosure breaks the Indexbar. Not a big problem for me, I don't' want to use it, but can of course be a problem for others.

This is the Javascript error: Uncaught TypeError: Cannot call method 'getCount' of undefined (line 634).

Edit:

I also notice some lag when scrolling fast through the list. And when I go to the detail pane (fade) and go back again, the fade transition is weird. It flickers and it seems the Buflist is loaded before the fade ends.

Edit:

Seems that all animations between the panels and the Buflist is weird. Doesn't happen in Chrome or the iPhone/iPad simulator. Only on my iPhone 4 device. Maybe someone knows how to fix this, I'm lost on this one.

Dannydekr
26 Aug 2011, 2:22 AM
Hello
I posted in the old thread so I will also post my problem here.

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?

Yeah that sounds like the problem I have. I don't use any images in the item list though. I do use a background-gradient, but when I disabled it it's still laggy like you describe. I guess you also have the same problems with the animations as I have. I wonder if you also have it on the iDevice only, and not in the browser/simulator.

I tried to figure it out for a few hours, but I can't seem to find the problem. The Ext.List component doesn't have this problem (however, loading times when clicking an item are horrible, so Bufferedlist is still the best solution).

Still hope someone can help.

pat777
31 Oct 2011, 10:52 PM
Hi,


first of all, I want to say thank you for sharing this code with the community - it really helped me a lot!


Since I needed a component which deals with large data sets and also handles updates on the store in the view accordingly, I took a closer look at the BufferedList but I ran into some issues with the control: When the bound store is updated within a timer, the refresh method of BufferedList is called, which does not seem to handle the scroll position in the right way. That sometimes leads to a "white list scenario" (it seems to scroll into topProxy and/or bottomProxy area in some situations). I wrote some code to fix that issue (just trying to hit the right scroll position in refresh() function), but it does not seem to help in every update scenario - there were always situations, when the scroll position of the list was unexpectedly outside the bounds of listContainer element :(.


So, after a lot of testing with BufferedList I decided to take some useful parts of the Ext.ux.BufferedList implementation and write my own component "Ext.ux.VirtualList" which definitely does not offer as many features as Ext.ux.BufferedList, but it handles updates on the store a bit better (IMO). The whole thing starts as an education project (I just wanted to learn writing my own components in Sencha Touch and implement my own concept of a list control ) but after a while it turns out to be useful in one of my projects, so maybe someone out there considers this code helpful too :). My List implementation should be an alternative, not a replacement - there are still many features missing in my implementation. Anyway: I want to share this code with you - maybe it is useful for other people like the BufferedList was useful to me in first place.


You can find the implementation of my list control (which I called "Ext.ux.VirtualList") here: https://github.com/pat777/VirtualList.


Hope you enjoy it! Have a nice day everybody!

Surykat
15 Nov 2011, 2:43 AM
Is there any roadmap to rebuild that component for Sencha Touch 2??

MahlerFreak
26 Nov 2011, 2:50 PM
Is there any roadmap to rebuild that component for Sencha Touch 2??

Hi all, I've been off on other life missions for a few months. I will take a look at porting the list to latest sencha touch this coming week, as well as the number of other issues mentioned in this thread.

SunboX
26 Nov 2011, 9:18 PM
Great news!

Btw, ST2 does a really good job with large lists. ;o) But would be nice to have this component for really laaaarge lists. :D

greetings Sunny

ssameer82
9 Dec 2011, 7:51 AM
Thanks for providing such a component. I tried this for populating large amount of data in a list from JSON STORE (more than 300) but after scrolling down (above 100 data's) not showing it in the list and getting scrolled with blank screen. Please help me out on this..

MahlerFreak
10 Dec 2011, 12:45 PM
Thanks for providing such a component. I tried this for populating large amount of data in a list from JSON STORE (more than 300) but after scrolling down (above 100 data's) not showing it in the list and getting scrolled with blank screen. Please help me out on this..

You'll have to be much more specific, with sample code, in order for me to help. As you can see by running the examples, or from reading this thread, this is not a problem most people are having.

janelle
3 Jan 2012, 4:25 PM
I tried this plugin, and it actually makes my problems worse not better. It makes the scrolling much more jumpy. I fiddled with the config settings to see if it made a difference, but not really. Its much worse on the iPhone than on my Nexus S.

I have a list of images. Each list item is an image (with two little buttons overlayed on top) with the width set to 100% and the height is dynamic (img tag inside the div pushes the div to the proper height). There are only 66 items in my list at this point, but its scrolling badly already (will need to have up to 150 images).

Has anyone else done a large list of images? Is there anything I can do to make it work better....with or without this plugin?

Will the scrolling with large lists work better in Sencha 2.0?

MahlerFreak
5 Jan 2012, 11:35 AM
The BufferedList is designed to deal with the scrolling performance degradation caused by lists with a large number of items. It does this by incrementally rendering items as you scroll, and I can believe that incrementally rendering images (as opposed to text or other faster rendering items) will cause scrolling to be worse. Note that BufferedList does not change the basic scrolling algorithms in Sencha Touch, it only manages the number of items being scrolled.

I wouldn't hold out too much hope for major improvement in 2.0. If you check out the Twitter feed example in 2.0 - http://docs.sencha.com/touch/2-0/touch/examples/twitter/index.html - you'll see an example that uses more complex markup with images included, and you'll note that it is usable but definitely not as fluid as native app scrolling. I think the ultimate answer is native browser support for scrolling overflow content - which is, in fact, supported in iOS5, but not really integrated into Sencha Touch yet. You might want to experiment with this yourself - see http://johanbrook.com/browsers/native-momentum-scrolling-ios-5/ for one example.

Vis
12 Jan 2012, 2:17 AM
Hi! Thanks for your list.
But I have an issue. If items in store are not ordered by "groupfield" field, groupheaders are not working correctly.
For example, store items in this order

item1 = {groupfield: '1'};
item2 = {groupfield: '2'};
item3 = {groupfield: '1'},
item4 = {groupfield: '1'},
item5 = {groupfield: '1'},

then list would look something like this:

groupheader: 1
item1
groupheader: 2
item2
item3
item4
item5

P.S. sorry for my english

EDIT: solve the problem by sorting the store :)

anand.arvind
8 Feb 2012, 2:35 AM
Thanks for sharing this code.

I am trying to use this to overcome memory issues in long lists on the ipad which crashes my application and found things work. I was using XTemplates to create each item and this did not work, have to debug to see how to get this fixed (got the latest version of the code from github). I am also adding the ListPaging plugin with this to load as one scrolls down to minimize loading from the DB in one shot for long list of records (2000+), and this seems to be working except for when one clicks on the loading spinner when loading.

Will post back with the updated code and solution once it is done, see this as a must needed component in the core system for anyone using this for real data.

prasanna_hr
26 Feb 2012, 7:53 AM
Hi,

Do you have a Sencha Touch 2 implementation of this?

Thanks
Prasanna


This is the first functionality-complete release of the list component I and others have been working on in this thread:

http://www.sencha.com/forum/showthread.php?119185-List-performance-consolidation-thread/

Ext.ux.BufferedList is designed to be used in place of the standard Ext.List component, and supports essentially all Ext.List functions and configuration parameters, while providing the following enhancements and changes:

- Adequate scrolling performance with large data sets. The attached examples use a data array of about 1400 items, while still providing similar scroll performance to Ext.List. This is accomplished by rendering a "sliding window" of list items based on current scroll position, rather than rendering all list items.
- Independent support for indexBar and group headers. By setting the standard list config parameter "grouped" to true, and the extension config parameter useGroupHeaders to false, you can use an index bar without having group headers, as some native iOS applications do. Obviously, setting useGroupHeaders to true gives you standard group headers.
- The configuration parameter "blockScrollSelect" is provided. If set to true, this prevents item selection while the list is still scrolling, so you can tap the list to stop scroll without invoking a selection - again, similar to native iOS. See the attached examples.

The only non-intuitive configuration is a parameter called "maxItemHeight". This is set at 85 pixels by default. If you have a significant number of items in the list which will be greater than this height, you should increase the value at least to the 90th percentile or so item height, to avoid potential problems with long scrolls to the top of the list. Also, itemTpl should be specified only as a string, not as an XTemplate, although this restriction may be removed soon.

I've licensed this work under either GPL or MIT, which I believe should allow for any forseeable reuse.

Please let me know of any bugs, enhancements, etc.

The attached file contains both the UxBufList.js file, and three examples demonstrating regular, indexBar, and grouped with headers lists. You will need to edit the html files to point to your sencha touch library.

Edit: I have joined the 21st Century and will not be posting updates in zip file format any longer. The latest will always be found here:

https://github.com/Lioarlan/UxBufList-Sench-Touch-Extension

MahlerFreak
26 Feb 2012, 12:03 PM
Hi,

Do you have a Sencha Touch 2 implementation of this?

Thanks
Prasanna

Unfortunately, no. I had a version about ready to go on top of ST2 PR3, and then there were major changes to Store, DataView, and List following PR3, which will require major rewrites and a new extension of Store. Due to a very busy schedule at a new job, I don't expect to be able to complete this any time soon.

I would hope/expect that this will be on the radar screen as core ST functionality sometime soon.

prasanna_hr
26 Feb 2012, 5:46 PM
Can I have the code that was working on ST2PR3? We have a need to show lists of large size (100-500). I can spend some time in changing the code to work on ST2RC.
Thanks
Prasanna


Unfortunately, no. I had a version about ready to go on top of ST2 PR3, and then there were major changes to Store, DataView, and List following PR3, which will require major rewrites and a new extension of Store. Due to a very busy schedule at a new job, I don't expect to be able to complete this any time soon.

I would hope/expect that this will be on the radar screen as core ST functionality sometime soon.

MahlerFreak
27 Feb 2012, 3:46 PM
Can I have the code that was working on ST2PR3? We have a need to show lists of large size (100-500). I can spend some time in changing the code to work on ST2RC.
Thanks
Prasanna

Prasanna,

PM sent.

Scott

kwach
4 Mar 2012, 2:48 PM
MahlerFreak,

I'm very interested in this component. Could you please send me the code too?

I'm using PR3 right now, but I'm going to port to RC version soon. I'd gladly help with porting of the component to RC.

Thanks!

EdouardG
12 Mar 2012, 5:07 PM
Hi there,
I'm very interested too, and willing to help :-)

--
Edouard

eeldwin
28 Mar 2012, 7:37 PM
We are building an app with one thousand items in the list. On android the standard Sencha list view is unusable, it is so slow. Buffered list in sencha one was fantastic.

We are desperate to see buffered list working in Sencha 2. If there is any thing that we can do to help get Buffered list working in sencha 2, let us know soon.

Thanks

larse503
7 Apr 2012, 2:53 PM
This sounds great... looking like it'll be a while for Sencha Touch 2, though? If possible, could someone send me the current version of the code for ST2? I could try to help.

THanks

MahlerFreak
16 Apr 2012, 2:37 PM
By popular request, attached is the code, with samples, that was mostly working with ST2 PR3. After PR3, there were significant changes to both the Store and DataView classes, which will require this code to be quite significantly reworked.

I am buried in a new job, and unfortunately have almost little time to help on the port.

Good luck.

eeldwin
23 Apr 2012, 4:51 PM
anyone interested to build this component collaboratively on Git Hub?

MahlerFreak
24 Apr 2012, 9:06 AM
anyone interested to build this component collaboratively on Git Hub?

I think that the new infinite grid implementation in ExtJS 4.1 is a likely starting point. Just a suggestion.

eeldwin
24 Apr 2012, 8:25 PM
just wondering, how do you add this component to sencha touch 2? do i need to tweak the sencha js file?

yarus23
26 Apr 2012, 11:59 AM
It is used in real project by many users. I have made some fixes and improvements. My list has got 3-4 em items in height so I have made some improvements for it.

robertklep
29 Apr 2012, 12:53 AM
anyone interested to build this component collaboratively on Git Hub?

I took a shot at making this work on ST2. At least it's showing a list and I can see it rendering on demand, but there's still lots of bugs to iron out.

Here it is: https://github.com/robertklep/buffered-ext-list-sencha-touch-2

FWIW: I'm not an experienced Sencha Touch programmer, only been working with it for a week or so. More experienced programmers are very welcome :)

jep
29 May 2012, 5:37 PM
Sencha either needs to get their own version of this out the door, or at least sit a programmer down with this one and updated it for 2.0.1. This is such a crucial feature. Without it, the world of useful ST2 apps is quite unreasonably constrained.

I wish Sencha could at least post something on the subject.

blackey
15 Jun 2012, 12:45 PM
Hello Everyone,

I'm attempting to replace all of the uses xtype 'list' to this awesome bufferedList in my application and I'm seeing something weird and was wondering if anyone else has ran into this.

In the plugin's refresh method the call to 'this.listContainer = this.getTargetEl().down('.ux-list-container');' returns null to the listContainer and from there all kinds of problems arise.

Is there a reason the '.ux-list-container' wouldn't be found?

suzzer99
26 Jun 2012, 5:45 PM
Is this still coming along? We're trying to upgrade to Sencha 2 - but we use this buffered list class for a channel lineup that displays over 1000 channels. I'm really hoping this thing gets finished before then or Sencha implements infinite scrolling - as they said they would for 2.0.

Also here's an interesting blog post about speeding up the buffered list class itself: http://rumblings.yellresearch.com/?p=144

jep
26 Jun 2012, 8:28 PM
I read that blog post and it doesn't seem to talk about speeding up the buffered list class at all. It seems to be about "don't use tables in your itemTpl". This is a painful lesson I learned for myself.

Or am I misreading it?

BTW, I have been using the ST2 version of the buffered list robertklep put up on github. I fixed a lot of the bugs and have it working pretty well. Scrolling isn't like regular lists in that it seems like it "hitches" after a certain amount when you flick it really fast. But I think I got all the bugs out and I re-implemented the code that creates the groupings (it had bugs if you wanted anything other than grouping by lowercase first letter).

My fork is at:
https://github.com/jepp/buffered-ext-list-sencha-touch-2

I checked in my changes and initiated a pull request to robertklep. Hopefully I didn't screw it up but then git still confuses me.

rutzmic
3 Jul 2012, 5:44 AM
Thanks a lot for this amazing component! Realy a live saver! Now works proper on Android too since the last update. @Sencha - Team, please consider adding this to the Touch2 Framework!

jeroenwalter
22 Aug 2012, 2:57 PM
Thank you very much for this component and thank jepp for his bugfixes.
This component really should be part of Sencha Touch 2.

jep
22 Aug 2012, 5:12 PM
You're welcome! Make sure you use robertklep's github, as he's integrated my changes (after a comedy of errors with me trying to make pull requests) as well as others.

robertklep
22 Aug 2012, 10:10 PM
You're welcome! Make sure you use robertklep's github, as he's integrated my changes (after a comedy of errors with me trying to make pull requests) as well as others.

Which reminded me that I hadn't yet merged your changes to the master branch on github. Which I just did :)

jeroenwalter
23 Aug 2012, 4:53 AM
Thanks for pointing out that I need to use Robert's version (big thanks to him too of course !).

jep
23 Aug 2012, 5:16 AM
Which reminded me that I hadn't yet merged your changes to the master branch on github. Which I just did :)

Which reminds me that I still need to give you good examples of the case sensitivity thing. :D

jeroenwalter
24 Aug 2012, 3:28 PM
Hi

I have encountered 2 issues with the buffered list (using sencha touch 2.1.0-beta2):
1. usage in a nestedlist
If I use it as the list in a nestedlist by setting the xtype of the listconfig to 'bufferedlist', then the itemtaps of the bufferedlist don't fire to the nestedlist. The effect is that the nestedlist is useless, it no longer handles the itemtaps and leafitemtaps of the list, so navigation is broken.

2. exception when changing the store of a bufferedlist
Maybe I'm doing something wrong but the following code generates an exception in the method Ext.dataview.element.Container.moveItemsToCache(from, to)
It is called with from = 0 and to = 0, and then calls me.getViewItems() which returns an empty array.
The empty array then is used with index 0, resulting in an exception.

I don't know if this is a bug in the bufferedlist or a bug in sencha, but the same code runs as expected with the normal list.

The exception occurs before the new store is loaded.



SetStore: function(params, title)
{
var me = this,
oldstore = me.getComiclistview().getStore();

var store = Ext.create('Comic.store.ComicList');
var url = params.url;
if (params.filter)
url += '?$filter=' + params.filter;

store.getProxy().setUrl(url);

me.getComiclistview().setStore(store);

if (oldstore)
oldstore.destroy();
}


stack trace:


Uncaught TypeError: Cannot call method 'destroy' of null Container.js:259
Ext.define.moveItemsToCacheContainer.js:259
Ext.define.onStoreClearDataView.js:962
Base.implement.callParentsencha-touch.js:4515
Ext.define.onStoreClearList.js:376
Base.implement.callParentsencha-touch.js:4515
Ext.define.onStoreClearBufferedList.js:1160
Ext.define.updateStoreDataView.js:751
Base.implement.callOverriddensencha-touch.js:4580
(anonymous function)Mixin.js:40
Base.implement.callParentsencha-touch.js:4515
Ext.define.updateStoreBufferedList.js:76
(anonymous function)sencha-touch.js:3252
Ext.apply.generateSetter.settersencha-touch.js:5284
Ext.define.SetStore


I will take a shot to solve this myself, but any help or insights will be appreciated.

jeroenwalter
25 Aug 2012, 3:02 AM
https://github.com/jeroenwalter/buffered-ext-list-sencha-touch-2

- Using the buffered list in a nestedlist now correctly passes the itemtap events.
- base DataView now uses the viewItemArray instead of own item selector.

jeroenwalter
25 Aug 2012, 3:05 AM
I think it would be best if we move the discussion of the buffered list to the Sencha Touch 2.x: Examples and Showcases forum.

jep
28 Aug 2012, 8:44 AM
I think before really fixing anything you should move to the newest ST. If you make your fixes against the beta, they're very likely to come "unfixed" when used against the final release.

Nevermind. I misread your "2.1.0-beta2" as "2.0.1-beta2". (I've also been away from ST for a bit so I wasn't up on the 2.1 beta.) You're still probably going to be fixing stuff that'll come unfixed, but there's not much else you can do about it if you want to try to get a jump on 2.1.

jeroenwalter
28 Aug 2012, 11:54 PM
I think before really fixing anything you should move to the newest ST. If you make your fixes against the beta, they're very likely to come "unfixed" when used against the final release.

Nevermind. I misread your "2.1.0-beta2" as "2.0.1-beta2". (I've also been away from ST for a bit so I wasn't up on the 2.1 beta.) You're still probably going to be fixing stuff that'll come unfixed, but there's not much else you can do about it if you want to try to get a jump on 2.1.

I know.
I'm still not sure if the fixes I made are the best or if they are simply workarounds.
I need to test it more, but right now it seems to work for me.
I still get some strange repaint issue sometimes when I change the store instance of a buffered list with setStore(), so I'll look into that.

MahlerFreak
12 Sep 2012, 1:16 PM
For all following this thread in hopes of a 2.x port of BufferedList, please note that the latest release of 2.1 Beta (Beta 3) includes Sencha's own implementation of this concept, which should be both more functional and better supported :)

Thanks to all who contributed to this original version.

jep
12 Sep 2012, 1:36 PM
That's nice to know. Might have to hold out on releasing my project until 2.1 gets its GPL release.

Have you played around with it much? Can you comment on how good it is compared to yours and some of the other ports?

jeroenwalter
12 Sep 2012, 11:08 PM
I hope it also supports DataView so I can create custom list components.

Choda
14 Sep 2012, 12:10 AM
I am working on a commercial project which ABSOLUTELY hinges on infinite scrolling. I've tried implementing robertklep's buffered scrolling, but while it does display the list, the scrolling essentially doesn't work... at all. I'm about to try jeroenwalter's fork and see if I get lucky with that one... That being said, how safe is it to port the project to whatever the current release version of 2.1 is? Is there any ETA on a stable release of 2.1? Thanks in advance.

jeroenwalter
14 Sep 2012, 1:20 AM
If scrolling still doesn't work with my fork, then I think you may have some other issues.
Maybe you've disabled scrolling for the list container or something like that.
Try the normal Ext.List first, if that doesn't scroll as well, the fault must be in your code.
If not, then something may be wrong in the buffered list.

I'm using the buffered list in a project where I'm currently displaying about 11000 items and it works as expected.

I'm using ST 2.1.0 beta 2 for this, I haven't tested it with beta 3 yet, so maybe that will break it.

Choda
14 Sep 2012, 1:38 AM
I am going to test it within the next hour, but the scrolling does work for me with a normal list. However, I believe the reason why this might pose an issue is the fact that my "list" is actually a grid, or to be more specific, an image gallery. So there are 3-4 inline items depending on the screen resolution. Could this have a bearing?

jeroenwalter
14 Sep 2012, 2:24 AM
Shouldn't be a problem, as long as you adhere to the List and the itemtemplate mechanism it uses.
I show for each item an image with a couple of lines of text.

For a screenshot:

http://www.badaap.nl/wordpress/wp-content/gallery/badaap-comicrack-plugin/tablet_series.jpg

The list on the right is the buffered list and loads 500 items per 'page'. So after scrolling down 500 items, 500 new items are loaded.

The list on the left too, but it loads all items at once. But it shows the index bar and grouping working with the buffered list.

Choda
14 Sep 2012, 3:50 AM
Alright so, tested it on a list containing 80 pictures, 3 per row, it doesn't work I'm afraid. The framerate drops to 0 essentially, scrolling is choppy and often just freezes completely. :(

EDIT: For reference, this is my code:


var picStore = Ext.create('Ext.data.Store', {
storeId: 'picStore',
id: 'pic_store',
fields: ['id', 'size', 'src'],
});

var picContainer = Ext.create('Ext.ux.BufferedList', {
inline: true,
margin: 0,
padding: 0,
id: 'pic_container',
store: 'picStore',
itemTpl: '<div style="margin: 0; padding 0; width:{size}px; height:{size}px; background-image: url({src}); background-size: cover; background-position: center;"></div>',
listeners: {
select: this.handleSelect,
},

});

Tried changing the itemTpl to something like '{src}' only, didn't help.

jeroenwalter
14 Sep 2012, 3:53 AM
Alright so, tested it on a list containing 80 pictures, 3 per row, it doesn't work I'm afraid. The framerate drops to 0 essentially, scrolling is choppy and often just freezes completely. :(

But it does scroll.... you previously said it didn't scroll.
So you basically have a performance issue, not a functionality issue.

Do you have large images?
Or css box shadows or round corners or gradients? Those are performance killers with scrolling :(

Choda
14 Sep 2012, 3:55 AM
Just edited my post, none of that. But honestly, I can't say if it's scrolling a all... sometimes when I drag down (i.e. trying to scroll up), nothing happens for a few seconds, then it scrolls a few pixels in the opposite direction... If it's a performance issue, than it's a CRIPPLING issue, one which the normal list doesn't have.

And no, images are small thumbnails, that CSS there, coupled with a bit of float / margin stuff to make them line up properly is all there is =/

jeroenwalter
14 Sep 2012, 4:16 AM
You may try to set the maxItemHeight config option of the buffered list to the expected height of an item, maybe that helps?

You can also take a look at my code, maybe you'll see something that helps.

The entire project:
https://github.com/jeroenwalter/ComicRackWeb/tree/master/ComicRackWebViewer/tablet

The list:
https://github.com/jeroenwalter/ComicRackWeb/blob/master/ComicRackWebViewer/tablet/app/view/ComicList.js

jeroenwalter
14 Sep 2012, 4:21 AM
O, btw, you did enable the listpaging plugin and are using a data proxy?
Because otherwise it just may be that the list renders ALL items instead of only a small portion.

suzzer99
2 Oct 2012, 10:32 AM
For what it's worth, I have been testing the new Sencha List in Touch 2.1 beta 3. We still need the UxBuffered list for some of the Androids we test on.

We have a channel list with about 1200 items. From what I can tell on the problem Androids (HTC LTE), the new Sencha List performs about the same as the old Sencha List. The UxBuffered list does work fine extending the new List.

For us anyway it looks like most Androids will be able to render a list of 1200 without seizing up fairly soon.

The only reason I really want to switch is there's a bug in the UxBuffered list where if you click a letter on the Index Bar that has no associated items - the list goes blank until the user scrolls a little.

jep
2 Oct 2012, 10:47 AM
Anyone know if packaging your app as a native app using PhoneGap gets around any of this performance problems (in 2.1 beta 3 or otherwise), or if the embedded browser has the exact same drawbacks?

chamijain
7 Jun 2013, 2:40 AM
Hello,

I am creating a image (grid) gallery view which fetch and load the images. But the preoblem is that
my application is getting slow and hanged when I am trying to load the image grid(gallery) view. Can anyone help me to resolve this problem.

neethul
22 Sep 2013, 11:01 AM
Is there Ext.ux.BufferedList support for Sencha Touch 2.2.1
We are facing serious issue on the list performance for 1000 items.