/** * 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.define('TweetList', { * extend: 'Ext.List', * * config: { * 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(''), /** * */ loadMoreCmp: { xtype: 'component', baseCls: Ext.baseCSSPrefix + 'list-paging' }, /** * @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(); this.setList(list); this.setScroller(scroller); list.getStore().on('load', this.onStoreLoad, this); // Disable main list load mask as we provide our own list.setLoadingText(null); if (this.getAutoPaging()) { scroller.on({ scrollend: this.onScrollEnd, scope: this }); } }, /** * @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() }), 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, position) { if (!this.getLoading() && position.y >= scroller.maxPosition.y) { if (!this.storeFullyLoaded()) { 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 */ onStoreLoad: function(store) { var loadCmp = this.addLoadMoreCmp(), template = this.getLoadTpl(), message = this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText(); this.setLoading(false); //restores scroll position after a Store load if (this.scrollY) { this.getScroller().scrollTo(null, this.scrollY); delete this.scrollY; } //if we've reached the end of the data set, switch to the noMoreRecordsText loadCmp.setHtml(template.apply({ cssPrefix: Ext.baseCSSPrefix, message: message })); }, /** * @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); 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() { this.setLoading(true); //keep a cache of the current scroll position as we'll need to reset it after the List is //updated with new data this.scrollY = this.getScroller().position.y; this.getList().getStore().nextPage({ addRecords: true }); } });