Sencha: Load data on demand

UPDATE – 1/4/2011

Got rid of flickering during the scrolling. Ext.DataView cleanup all inner items before store load event and then redraw all the items once store was changed. So I optimized the way to render new records – just append new DOM elements (without touching already rendering), so less DOM operations/redraw required and now it works much faster and provide good user experience.

After spending some time playing with Sencha Touch on my iPhone, I figured out that mobile devices/browsers.have some limitations:

  • pure hardware capability comparing to desktop machines/browsers. So rendering huge amount of DOM elements may be a performance hit.
  • AT&T’s 3G internet is not so fast as I’d wish. And it costs money…

That’s why I come up with idea to bring lazy loading capability to Sencha Touch framework. It make sense to extend one of the fundamental components of the framework in order to make it easier to use the feature in different controls. Therefore I decided to start with Ext.DataView. The main purpose of the enhanced control will be to determine when more data needed (to display to user), how many records are required. Then it will request necessary amount of data, and finally render new data. Sounds simple and straightforward, isn’t it?

WHEN

From user’s perspective of view there should be feeling of “infinite” flow of data. User may request next portion of data in many different ways, but let’s consider the simplest and more common way – scrolling. There are no interactive scrollbars on mobile Safari browser so elements with overflow:scroll on them appear to be clipped. Any attempts to scroll them via the normal single finger touch mechanism results in the entire page scrolling. The only way to scroll the content is to use a 2 finger swipe which scrolls the content inside the element. This scrolling seems very crude and does not implement the smooth momentum scrolling used by the main browser canvas and native scrolling panels.

Fortunately Sencha Touch has ScrollView control which provide the native scrolling experience on iDevices for any DOM element. ScrollView instance is a part of base Component control, so we can access it via public scroller property. We’re interesting in scroll event as in a point when we possibly need more data:

//listen for scroll event
this.scroller.on('scroll', this.onScroll, this);

HOW MANY

One of the parameters passed to onScroll function will be offset object, with two properties x and y. By knowing how many pixels are out of the visible area we may determine what exactly rows user is watching:

   1: onScroll : function(scroller, offset) {

   2:     var store = this.store;

   3:     var pageSize = this.pageSize;

   4:     var oy = offset.y;

   5:     var itemsCount = oy / this.itemHeight;

   6:     // how many items were scrolled on current page

   7:     var c = itemsCount % pageSize;

   8:     var isLastPage = (itemsCount / pageSize) >= (store.currentPage - 2);

   9:     if (c / pageSize >= this.loadBarrier && !store.isLoading()

  10:             && isLastPage) {

  11:         store.nextPage();

  12:     }

  13: },

But there are two unknown (yet) variables: this.pageSize and this.itemHeight. As DataView may contain very custom items in it, there is no other way to measure it’s height than by render it and get it’s size. But we need itemHeight before we have any data. This means we have to render “dummy” item and get it’s size:

   1: getItemHeight : function() {

   2:     var el = this.getTargetEl();

   3:     var div = document.createElement('div');

   4:     var model = this.store.getProxy().getModel();

   5:     var data = {};

   6:     Ext.each(model.prototype.fields.items, function(field){

   7:         data[field.name] = ' ';

   8:     }, this);

   9:     var item = this.tpl.overwrite(div, [data]);

  10:     el.appendChild(item);

  11:     var height = item.offsetHeight;

  12:     Ext.removeNode(item);

  13:     return height;

  14: },

Then it’s easy to calculate amounts of visible rows (pageSize):

var pageSize = Math.round(height / this.itemHeight);

REQUEST

Each DataView has it’s own underlying store. The component is more useful with async stores. Store provide great paging feature, so let’s use it. For better user’s experience let’s pre-load one page forward and do next request when half of the pre-loaded page is viewed. You can configure percentage of rows to be viewed on pre-loaded page before next page will be pre-loaded. This can be done by using loadBarrier property.

if (c / pageSize >= this.loadBarrier && !store.isLoading() && isLastPage) {
store.nextPage();
}
 
Where “c” is amount of visible rows on current page (pre-loaded one).

RENDER

Fortunately DataView monitor store’s data change and will update DOM when new data comes from the server.

Usage

There is nothing special in Ext.ux.touch.LazyDataView usage comparing to Ext.DataView:

   1: new Ext.ux.touch.LazyDataView({

   2:         store: store,

   3:         tpl: '<tpl for="."><div class="contact"><strong>{firstName}</strong> {lastName}</div></tpl>',

   4:         autoHeight:true,

   5:         multiSelect: true,

   6:         overClass:'x-view-over',

   7:         itemSelector:'div.contact',

   8:         emptyText: 'No contacts to display'

   9: })

You may find more details in Sencha’s API documentation.

Conclusion

Well, I believe the extension’s lifecycle and purpose are clear. Example of LazyDataView you may find on my playground. Sources you may grab from GitHub repository. I’d appreciate any comments, questions, feature requests in comments to the post and if I find appropriate interest to the extension, I’ll continue work on it.

Related Articles

iScroll

TouchScroll

http://www.macresearch.org/dynamics-scrolling

  • Related posts
  • No related posts found
  • John Cheng

    Dear Pavel:

    thanks for your LazyDataView,seems it’s what I need for a large data listing
    But is it compatible with Sencha touch 2.x ?
    thanks for your reply