333 lines
9.9 KiB
JavaScript
333 lines
9.9 KiB
JavaScript
/**
|
|
* Adds a Load More button at the bottom of the list. When the user presses this button,
|
|
* the next page of data will be loaded into the store and appended to the List.
|
|
*
|
|
* By specifying `{@link #autoPaging}: true`, an 'infinite scroll' effect can be achieved,
|
|
* i.e., the next page of content will load automatically when the user scrolls to the
|
|
* bottom of the list.
|
|
*
|
|
* ## Example
|
|
*
|
|
* Ext.create('Ext.dataview.List', {
|
|
*
|
|
* store: Ext.create('TweetStore'),
|
|
*
|
|
* plugins: [
|
|
* {
|
|
* xclass: 'Ext.plugin.ListPaging',
|
|
* autoPaging: true
|
|
* }
|
|
* ],
|
|
*
|
|
* itemTpl: [
|
|
* '<img src="{profile_image_url}" />',
|
|
* '<div class="tweet">{text}</div>'
|
|
* ]
|
|
* });
|
|
*/
|
|
Ext.define('Ext.plugin.ListPaging', {
|
|
extend: 'Ext.Component',
|
|
alias: 'plugin.listpaging',
|
|
|
|
config: {
|
|
/**
|
|
* @cfg {Boolean} autoPaging
|
|
* True to automatically load the next page when you scroll to the bottom of the list.
|
|
*/
|
|
autoPaging: false,
|
|
|
|
/**
|
|
* @cfg {String} loadMoreText The text used as the label of the Load More button.
|
|
*/
|
|
loadMoreText: 'Load More...',
|
|
|
|
/**
|
|
* @cfg {String} noMoreRecordsText The text used as the label of the Load More button when the Store's
|
|
* {@link Ext.data.Store#totalCount totalCount} indicates that all of the records available on the server are
|
|
* already loaded
|
|
*/
|
|
noMoreRecordsText: 'No More Records',
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {String} loadTpl The template used to render the load more text
|
|
*/
|
|
loadTpl: [
|
|
'<div class="{cssPrefix}loading-spinner" style="font-size: 180%; margin: 10px auto;">',
|
|
'<span class="{cssPrefix}loading-top"></span>',
|
|
'<span class="{cssPrefix}loading-right"></span>',
|
|
'<span class="{cssPrefix}loading-bottom"></span>',
|
|
'<span class="{cssPrefix}loading-left"></span>',
|
|
'</div>',
|
|
'<div class="{cssPrefix}list-paging-msg">{message}</div>'
|
|
].join(''),
|
|
|
|
/**
|
|
* @cfg {Object} loadMoreCmp
|
|
* @private
|
|
*/
|
|
loadMoreCmp: {
|
|
xtype: 'component',
|
|
baseCls: Ext.baseCSSPrefix + 'list-paging',
|
|
scrollDock: 'bottom',
|
|
hidden: true
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {Boolean} loadMoreCmpAdded Indicates whether or not the load more component has been added to the List
|
|
* yet.
|
|
*/
|
|
loadMoreCmpAdded: false,
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {String} loadingCls The CSS class that is added to the {@link #loadMoreCmp} while the Store is loading
|
|
*/
|
|
loadingCls: Ext.baseCSSPrefix + 'loading',
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {Ext.List} list Local reference to the List this plugin is bound to
|
|
*/
|
|
list: null,
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {Ext.scroll.Scroller} scroller Local reference to the List's Scroller
|
|
*/
|
|
scroller: null,
|
|
|
|
/**
|
|
* @private
|
|
* @cfg {Boolean} loading True if the plugin has initiated a Store load that has not yet completed
|
|
*/
|
|
loading: false
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Sets up all of the references the plugin needs
|
|
*/
|
|
init: function(list) {
|
|
var scroller = list.getScrollable().getScroller(),
|
|
store = list.getStore();
|
|
|
|
this.setList(list);
|
|
this.setScroller(scroller);
|
|
this.bindStore(list.getStore());
|
|
|
|
this.addLoadMoreCmp();
|
|
|
|
// The List's Store could change at any time so make sure we are informed when that happens
|
|
list.updateStore = Ext.Function.createInterceptor(list.updateStore, this.bindStore, this);
|
|
|
|
if (this.getAutoPaging()) {
|
|
scroller.on({
|
|
scrollend: this.onScrollEnd,
|
|
scope: this
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
bindStore: function(newStore, oldStore) {
|
|
if (oldStore) {
|
|
oldStore.un({
|
|
beforeload: this.onStoreBeforeLoad,
|
|
load: this.onStoreLoad,
|
|
filter: this.onFilter,
|
|
scope: this
|
|
});
|
|
}
|
|
|
|
if (newStore) {
|
|
newStore.on({
|
|
beforeload: this.onStoreBeforeLoad,
|
|
load: this.onStoreLoad,
|
|
filter: this.onFilter,
|
|
scope: this
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Removes the List/DataView's loading mask because we show our own in the plugin. The logic here disables the
|
|
* loading mask immediately if the store is autoloading. If it's not autoloading, allow the mask to show the first
|
|
* time the Store loads, then disable it and use the plugin's loading spinner.
|
|
* @param {Ext.data.Store} store The store that is bound to the DataView
|
|
*/
|
|
disableDataViewMask: function() {
|
|
var list = this.getList();
|
|
this._listMask = list.getLoadingText();
|
|
|
|
list.setLoadingText(null);
|
|
},
|
|
|
|
enableDataViewMask: function() {
|
|
if(this._listMask) {
|
|
var list = this.getList();
|
|
list.setLoadingText(this._listMask);
|
|
delete this._listMask;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
applyLoadTpl: function(config) {
|
|
return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
applyLoadMoreCmp: function(config) {
|
|
config = Ext.merge(config, {
|
|
html: this.getLoadTpl().apply({
|
|
cssPrefix: Ext.baseCSSPrefix,
|
|
message: this.getLoadMoreText()
|
|
}),
|
|
scrollDock: 'bottom',
|
|
listeners: {
|
|
tap: {
|
|
fn: this.loadNextPage,
|
|
scope: this,
|
|
element: 'element'
|
|
}
|
|
}
|
|
});
|
|
|
|
return Ext.factory(config, Ext.Component, this.getLoadMoreCmp());
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* If we're using autoPaging and detect that the user has scrolled to the bottom, kick off loading of the next page
|
|
*/
|
|
onScrollEnd: function(scroller, x, y) {
|
|
var list = this.getList();
|
|
|
|
if (!this.getLoading() && y >= scroller.maxPosition.y) {
|
|
this.currentScrollToTopOnRefresh = list.getScrollToTopOnRefresh();
|
|
list.setScrollToTopOnRefresh(false);
|
|
|
|
this.loadNextPage();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Makes sure we add/remove the loading CSS class while the Store is loading
|
|
*/
|
|
updateLoading: function(isLoading) {
|
|
var loadMoreCmp = this.getLoadMoreCmp(),
|
|
loadMoreCls = this.getLoadingCls();
|
|
|
|
if (isLoading) {
|
|
loadMoreCmp.addCls(loadMoreCls);
|
|
} else {
|
|
loadMoreCmp.removeCls(loadMoreCls);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* If the Store is just about to load but it's currently empty, we hide the load more button because this is
|
|
* usually an outcome of setting a new Store on the List so we don't want the load more button to flash while
|
|
* the new Store loads
|
|
*/
|
|
onStoreBeforeLoad: function(store) {
|
|
if (store.getCount() === 0) {
|
|
this.getLoadMoreCmp().hide();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onStoreLoad: function(store) {
|
|
var loadCmp = this.getLoadMoreCmp(),
|
|
template = this.getLoadTpl(),
|
|
message = this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText();
|
|
|
|
if (store.getCount()) {
|
|
loadCmp.show();
|
|
}
|
|
this.setLoading(false);
|
|
|
|
//if we've reached the end of the data set, switch to the noMoreRecordsText
|
|
loadCmp.setHtml(template.apply({
|
|
cssPrefix: Ext.baseCSSPrefix,
|
|
message: message
|
|
}));
|
|
|
|
if (this.currentScrollToTopOnRefresh !== undefined) {
|
|
this.getList().setScrollToTopOnRefresh(this.currentScrollToTopOnRefresh);
|
|
delete this.currentScrollToTopOnRefresh;
|
|
}
|
|
|
|
this.enableDataViewMask();
|
|
},
|
|
|
|
onFilter: function(store) {
|
|
if (store.getCount() === 0) {
|
|
this.getLoadMoreCmp().hide();
|
|
}else {
|
|
this.getLoadMoreCmp().show();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Because the attached List's inner list element is rendered after our init function is called,
|
|
* we need to dynamically add the loadMoreCmp later. This does this once and caches the result.
|
|
*/
|
|
addLoadMoreCmp: function() {
|
|
var list = this.getList(),
|
|
cmp = this.getLoadMoreCmp();
|
|
|
|
if (!this.getLoadMoreCmpAdded()) {
|
|
list.add(cmp);
|
|
|
|
/**
|
|
* @event loadmorecmpadded Fired when the Load More component is added to the list. Fires on the List.
|
|
* @param {Ext.plugin.ListPaging} this The list paging plugin
|
|
* @param {Ext.List} list The list
|
|
*/
|
|
list.fireEvent('loadmorecmpadded', this, list);
|
|
this.setLoadMoreCmpAdded(true);
|
|
}
|
|
|
|
return cmp;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
* Returns true if the Store is detected as being fully loaded, or the server did not return a total count, which
|
|
* means we're in 'infinite' mode
|
|
* @return {Boolean}
|
|
*/
|
|
storeFullyLoaded: function() {
|
|
var store = this.getList().getStore(),
|
|
total = store.getTotalCount();
|
|
|
|
return total !== null ? store.getTotalCount() <= (store.currentPage * store.getPageSize()) : false;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
loadNextPage: function() {
|
|
var me = this;
|
|
if (!me.storeFullyLoaded()) {
|
|
me.disableDataViewMask();
|
|
me.setLoading(true);
|
|
me.getList().getStore().nextPage({ addRecords: true });
|
|
}
|
|
}
|
|
});
|