2 * Adds a Load More button at the bottom of the list. When the user presses this button,
3 * the next page of data will be loaded into the store and appended to the List.
5 * By specifying `{@link #autoPaging}: true`, an 'infinite scroll' effect can be achieved,
6 * i.e., the next page of content will load automatically when the user scrolls to the
11 * Ext.create('Ext.dataview.List', {
13 * store: Ext.create('TweetStore'),
17 * xclass: 'Ext.plugin.ListPaging',
23 * '<img src="{profile_image_url}" />',
24 * '<div class="tweet">{text}</div>'
28 Ext
.define('Ext.plugin.ListPaging', {
29 extend
: 'Ext.Component',
30 alias
: 'plugin.listpaging',
34 * @cfg {Boolean} autoPaging
35 * True to automatically load the next page when you scroll to the bottom of the list.
40 * @cfg {String} loadMoreText The text used as the label of the Load More button.
42 loadMoreText
: 'Load More...',
45 * @cfg {String} noMoreRecordsText The text used as the label of the Load More button when the Store's
46 * {@link Ext.data.Store#totalCount totalCount} indicates that all of the records available on the server are
49 noMoreRecordsText
: 'No More Records',
53 * @cfg {String} loadTpl The template used to render the load more text
56 '<div class="{cssPrefix}loading-spinner" style="font-size: 180%; margin: 10px auto;">',
57 '<span class="{cssPrefix}loading-top"></span>',
58 '<span class="{cssPrefix}loading-right"></span>',
59 '<span class="{cssPrefix}loading-bottom"></span>',
60 '<span class="{cssPrefix}loading-left"></span>',
62 '<div class="{cssPrefix}list-paging-msg">{message}</div>'
66 * @cfg {Object} loadMoreCmp
71 baseCls
: Ext
.baseCSSPrefix
+ 'list-paging',
78 * @cfg {Boolean} loadMoreCmpAdded Indicates whether or not the load more component has been added to the List
81 loadMoreCmpAdded
: false,
85 * @cfg {String} loadingCls The CSS class that is added to the {@link #loadMoreCmp} while the Store is loading
87 loadingCls
: Ext
.baseCSSPrefix
+ 'loading',
91 * @cfg {Ext.List} list Local reference to the List this plugin is bound to
97 * @cfg {Ext.scroll.Scroller} scroller Local reference to the List's Scroller
103 * @cfg {Boolean} loading True if the plugin has initiated a Store load that has not yet completed
110 * Sets up all of the references the plugin needs
112 init: function(list
) {
113 var scroller
= list
.getScrollable().getScroller(),
114 store
= list
.getStore();
117 this.setScroller(scroller
);
118 this.bindStore(list
.getStore());
120 this.addLoadMoreCmp();
122 // The List's Store could change at any time so make sure we are informed when that happens
123 list
.updateStore
= Ext
.Function
.createInterceptor(list
.updateStore
, this.bindStore
, this);
125 if (this.getAutoPaging()) {
127 scrollend
: this.onScrollEnd
,
136 bindStore: function(newStore
, oldStore
) {
139 beforeload
: this.onStoreBeforeLoad
,
140 load
: this.onStoreLoad
,
141 filter
: this.onFilter
,
148 beforeload
: this.onStoreBeforeLoad
,
149 load
: this.onStoreLoad
,
150 filter
: this.onFilter
,
158 * Removes the List/DataView's loading mask because we show our own in the plugin. The logic here disables the
159 * loading mask immediately if the store is autoloading. If it's not autoloading, allow the mask to show the first
160 * time the Store loads, then disable it and use the plugin's loading spinner.
161 * @param {Ext.data.Store} store The store that is bound to the DataView
163 disableDataViewMask: function() {
164 var list
= this.getList();
165 this._listMask
= list
.getLoadingText();
167 list
.setLoadingText(null);
170 enableDataViewMask: function() {
172 var list
= this.getList();
173 list
.setLoadingText(this._listMask
);
174 delete this._listMask
;
181 applyLoadTpl: function(config
) {
182 return (Ext
.isObject(config
) && config
.isTemplate
) ? config
: new Ext
.XTemplate(config
);
188 applyLoadMoreCmp: function(config
) {
189 config
= Ext
.merge(config
, {
190 html
: this.getLoadTpl().apply({
191 cssPrefix
: Ext
.baseCSSPrefix
,
192 message
: this.getLoadMoreText()
194 scrollDock
: 'bottom',
197 fn
: this.loadNextPage
,
204 return Ext
.factory(config
, Ext
.Component
, this.getLoadMoreCmp());
209 * If we're using autoPaging and detect that the user has scrolled to the bottom, kick off loading of the next page
211 onScrollEnd: function(scroller
, x
, y
) {
212 var list
= this.getList();
214 if (!this.getLoading() && y
>= scroller
.maxPosition
.y
) {
215 this.currentScrollToTopOnRefresh
= list
.getScrollToTopOnRefresh();
216 list
.setScrollToTopOnRefresh(false);
224 * Makes sure we add/remove the loading CSS class while the Store is loading
226 updateLoading: function(isLoading
) {
227 var loadMoreCmp
= this.getLoadMoreCmp(),
228 loadMoreCls
= this.getLoadingCls();
231 loadMoreCmp
.addCls(loadMoreCls
);
233 loadMoreCmp
.removeCls(loadMoreCls
);
239 * If the Store is just about to load but it's currently empty, we hide the load more button because this is
240 * usually an outcome of setting a new Store on the List so we don't want the load more button to flash while
241 * the new Store loads
243 onStoreBeforeLoad: function(store
) {
244 if (store
.getCount() === 0) {
245 this.getLoadMoreCmp().hide();
252 onStoreLoad: function(store
) {
253 var loadCmp
= this.getLoadMoreCmp(),
254 template
= this.getLoadTpl(),
255 message
= this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText();
257 if (store
.getCount()) {
260 this.setLoading(false);
262 //if we've reached the end of the data set, switch to the noMoreRecordsText
263 loadCmp
.setHtml(template
.apply({
264 cssPrefix
: Ext
.baseCSSPrefix
,
268 if (this.currentScrollToTopOnRefresh
!== undefined) {
269 this.getList().setScrollToTopOnRefresh(this.currentScrollToTopOnRefresh
);
270 delete this.currentScrollToTopOnRefresh
;
273 this.enableDataViewMask();
276 onFilter: function(store
) {
277 if (store
.getCount() === 0) {
278 this.getLoadMoreCmp().hide();
280 this.getLoadMoreCmp().show();
286 * Because the attached List's inner list element is rendered after our init function is called,
287 * we need to dynamically add the loadMoreCmp later. This does this once and caches the result.
289 addLoadMoreCmp: function() {
290 var list
= this.getList(),
291 cmp
= this.getLoadMoreCmp();
293 if (!this.getLoadMoreCmpAdded()) {
297 * @event loadmorecmpadded Fired when the Load More component is added to the list. Fires on the List.
298 * @param {Ext.plugin.ListPaging} this The list paging plugin
299 * @param {Ext.List} list The list
301 list
.fireEvent('loadmorecmpadded', this, list
);
302 this.setLoadMoreCmpAdded(true);
310 * Returns true if the Store is detected as being fully loaded, or the server did not return a total count, which
311 * means we're in 'infinite' mode
314 storeFullyLoaded: function() {
315 var store
= this.getList().getStore(),
316 total
= store
.getTotalCount();
318 return total
!== null ? store
.getTotalCount() <= (store
.currentPage
* store
.getPageSize()) : false;
324 loadNextPage: function() {
326 if (!me
.storeFullyLoaded()) {
327 me
.disableDataViewMask();
329 me
.getList().getStore().nextPage({ addRecords
: true });