/** * As the number of records increases, the time required for the browser to render them increases. Paging is used to * reduce the amount of data exchanged with the client. Note: if there are more records/rows than can be viewed in the * available screen area, vertical scrollbars will be added. * * Paging is typically handled on the server side (see exception below). The client sends parameters to the server side, * which the server needs to interpret and then respond with the appropriate data. * * Ext.toolbar.Paging is a specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic * paging control. This Component {@link Ext.data.Store#load load}s blocks of data into the {@link #store} by passing * parameters used for paging criteria. * * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component} * * Paging Toolbar is typically used as one of the Grid's toolbars: * * @example * var itemsPerPage = 2; // set the number of items you want per page * * var store = Ext.create('Ext.data.Store', { * id:'simpsonsStore', * autoLoad: false, * fields:['name', 'email', 'phone'], * pageSize: itemsPerPage, // items per page * proxy: { * type: 'ajax', * url: 'pagingstore.js', // url that will load data with respect to start and limit params * reader: { * type: 'json', * root: 'items', * totalProperty: 'total' * } * } * }); * * // specify segment of data you want to load using params * store.load({ * params:{ * start:0, * limit: itemsPerPage * } * }); * * Ext.create('Ext.grid.Panel', { * title: 'Simpsons', * store: store, * columns: [ * { header: 'Name', dataIndex: 'name' }, * { header: 'Email', dataIndex: 'email', flex: 1 }, * { header: 'Phone', dataIndex: 'phone' } * ], * width: 400, * height: 125, * dockedItems: [{ * xtype: 'pagingtoolbar', * store: store, // same store GridPanel is using * dock: 'bottom', * displayInfo: true * }], * renderTo: Ext.getBody() * }); * * To use paging, pass the paging requirements to the server when the store is first loaded. * * store.load({ * params: { * // specify params for the first page load if using paging * start: 0, * limit: myPageSize, * // other params * foo: 'bar' * } * }); * * If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration: * * var myStore = Ext.create('Ext.data.Store', { * {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25}, * ... * }); * * The packet sent back from the server would have this form: * * { * "success": true, * "results": 2000, * "rows": [ // ***Note:** this must be an Array * { "id": 1, "name": "Bill", "occupation": "Gardener" }, * { "id": 2, "name": "Ben", "occupation": "Horticulturalist" }, * ... * { "id": 25, "name": "Sue", "occupation": "Botanist" } * ] * } * * ## Paging with Local Data * * Paging can also be accomplished with local data using extensions: * * - [Ext.ux.data.PagingStore][1] * - Paging Memory Proxy (examples/ux/PagingMemoryProxy.js) * * [1]: http://sencha.com/forum/showthread.php?t=71532 */ Ext.define('Ext.toolbar.Paging', { extend: 'Ext.toolbar.Toolbar', alias: 'widget.pagingtoolbar', alternateClassName: 'Ext.PagingToolbar', requires: ['Ext.toolbar.TextItem', 'Ext.form.field.Number'], /** * @cfg {Ext.data.Store} store (required) * The {@link Ext.data.Store} the paging toolbar should use as its data source. */ /** * @cfg {Boolean} displayInfo * true to display the displayMsg */ displayInfo: false, /** * @cfg {Boolean} prependButtons * true to insert any configured items _before_ the paging buttons. */ prependButtons: false, /** * @cfg {String} displayMsg * The paging status message to display. Note that this string is * formatted using the braced numbers {0}-{2} as tokens that are replaced by the values for start, end and total * respectively. These tokens should be preserved when overriding this string if showing those values is desired. */ displayMsg : 'Displaying {0} - {1} of {2}', /** * @cfg {String} emptyMsg * The message to display when no records are found. */ emptyMsg : 'No data to display', /** * @cfg {String} beforePageText * The text displayed before the input item. */ beforePageText : 'Page', /** * @cfg {String} afterPageText * Customizable piece of the default paging text. Note that this string is formatted using * {0} as a token that is replaced by the number of total pages. This token should be preserved when overriding this * string if showing the total page count is desired. */ afterPageText : 'of {0}', /** * @cfg {String} firstText * The quicktip text displayed for the first page button. * **Note**: quick tips must be initialized for the quicktip to show. */ firstText : 'First Page', /** * @cfg {String} prevText * The quicktip text displayed for the previous page button. * **Note**: quick tips must be initialized for the quicktip to show. */ prevText : 'Previous Page', /** * @cfg {String} nextText * The quicktip text displayed for the next page button. * **Note**: quick tips must be initialized for the quicktip to show. */ nextText : 'Next Page', /** * @cfg {String} lastText * The quicktip text displayed for the last page button. * **Note**: quick tips must be initialized for the quicktip to show. */ lastText : 'Last Page', /** * @cfg {String} refreshText * The quicktip text displayed for the Refresh button. * **Note**: quick tips must be initialized for the quicktip to show. */ refreshText : 'Refresh', /** * @cfg {Number} inputItemWidth * The width in pixels of the input field used to display and change the current page number. */ inputItemWidth : 30, /** * Gets the standard paging items in the toolbar * @private */ getPagingItems: function() { var me = this; return [{ itemId: 'first', tooltip: me.firstText, overflowText: me.firstText, iconCls: Ext.baseCSSPrefix + 'tbar-page-first', disabled: true, handler: me.moveFirst, scope: me },{ itemId: 'prev', tooltip: me.prevText, overflowText: me.prevText, iconCls: Ext.baseCSSPrefix + 'tbar-page-prev', disabled: true, handler: me.movePrevious, scope: me }, '-', me.beforePageText, { xtype: 'numberfield', itemId: 'inputItem', name: 'inputItem', cls: Ext.baseCSSPrefix + 'tbar-page-number', allowDecimals: false, minValue: 1, hideTrigger: true, enableKeyEvents: true, selectOnFocus: true, submitValue: false, width: me.inputItemWidth, margins: '-1 2 3 2', listeners: { scope: me, keydown: me.onPagingKeyDown, blur: me.onPagingBlur } },{ xtype: 'tbtext', itemId: 'afterTextItem', text: Ext.String.format(me.afterPageText, 1) }, '-', { itemId: 'next', tooltip: me.nextText, overflowText: me.nextText, iconCls: Ext.baseCSSPrefix + 'tbar-page-next', disabled: true, handler: me.moveNext, scope: me },{ itemId: 'last', tooltip: me.lastText, overflowText: me.lastText, iconCls: Ext.baseCSSPrefix + 'tbar-page-last', disabled: true, handler: me.moveLast, scope: me }, '-', { itemId: 'refresh', tooltip: me.refreshText, overflowText: me.refreshText, iconCls: Ext.baseCSSPrefix + 'tbar-loading', handler: me.doRefresh, scope: me }]; }, initComponent : function(){ var me = this, pagingItems = me.getPagingItems(), userItems = me.items || me.buttons || []; if (me.prependButtons) { me.items = userItems.concat(pagingItems); } else { me.items = pagingItems.concat(userItems); } delete me.buttons; if (me.displayInfo) { me.items.push('->'); me.items.push({xtype: 'tbtext', itemId: 'displayItem'}); } me.callParent(); me.addEvents( /** * @event change * Fires after the active page has been changed. * @param {Ext.toolbar.Paging} this * @param {Object} pageData An object that has these properties: * * - `total` : Number * * The total number of records in the dataset as returned by the server * * - `currentPage` : Number * * The current page number * * - `pageCount` : Number * * The total number of pages (calculated from the total number of records in the dataset as returned by the * server and the current {@link Ext.data.Store#pageSize pageSize}) * * - `toRecord` : Number * * The starting record index for the current page * * - `fromRecord` : Number * * The ending record index for the current page */ 'change', /** * @event beforechange * Fires just before the active page is changed. Return false to prevent the active page from being changed. * @param {Ext.toolbar.Paging} this * @param {Number} page The page number that will be loaded on change */ 'beforechange' ); me.on('afterlayout', me.onLoad, me, {single: true}); me.bindStore(me.store || 'ext-empty-store', true); }, // private updateInfo : function(){ var me = this, displayItem = me.child('#displayItem'), store = me.store, pageData = me.getPageData(), count, msg; if (displayItem) { count = store.getCount(); if (count === 0) { msg = me.emptyMsg; } else { msg = Ext.String.format( me.displayMsg, pageData.fromRecord, pageData.toRecord, pageData.total ); } displayItem.setText(msg); me.doComponentLayout(); } }, // private onLoad : function(){ var me = this, pageData, currPage, pageCount, afterText; if (!me.rendered) { return; } pageData = me.getPageData(); currPage = pageData.currentPage; pageCount = pageData.pageCount; afterText = Ext.String.format(me.afterPageText, isNaN(pageCount) ? 1 : pageCount); me.child('#afterTextItem').setText(afterText); me.child('#inputItem').setValue(currPage); me.child('#first').setDisabled(currPage === 1); me.child('#prev').setDisabled(currPage === 1); me.child('#next').setDisabled(currPage === pageCount); me.child('#last').setDisabled(currPage === pageCount); me.child('#refresh').enable(); me.updateInfo(); me.fireEvent('change', me, pageData); }, // private getPageData : function(){ var store = this.store, totalCount = store.getTotalCount(); return { total : totalCount, currentPage : store.currentPage, pageCount: Math.ceil(totalCount / store.pageSize), fromRecord: ((store.currentPage - 1) * store.pageSize) + 1, toRecord: Math.min(store.currentPage * store.pageSize, totalCount) }; }, // private onLoadError : function(){ if (!this.rendered) { return; } this.child('#refresh').enable(); }, // private readPageFromInput : function(pageData){ var v = this.child('#inputItem').getValue(), pageNum = parseInt(v, 10); if (!v || isNaN(pageNum)) { this.child('#inputItem').setValue(pageData.currentPage); return false; } return pageNum; }, onPagingFocus : function(){ this.child('#inputItem').select(); }, //private onPagingBlur : function(e){ var curPage = this.getPageData().currentPage; this.child('#inputItem').setValue(curPage); }, // private onPagingKeyDown : function(field, e){ var me = this, k = e.getKey(), pageData = me.getPageData(), increment = e.shiftKey ? 10 : 1, pageNum; if (k == e.RETURN) { e.stopEvent(); pageNum = me.readPageFromInput(pageData); if (pageNum !== false) { pageNum = Math.min(Math.max(1, pageNum), pageData.pageCount); if(me.fireEvent('beforechange', me, pageNum) !== false){ me.store.loadPage(pageNum); } } } else if (k == e.HOME || k == e.END) { e.stopEvent(); pageNum = k == e.HOME ? 1 : pageData.pageCount; field.setValue(pageNum); } else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN) { e.stopEvent(); pageNum = me.readPageFromInput(pageData); if (pageNum) { if (k == e.DOWN || k == e.PAGEDOWN) { increment *= -1; } pageNum += increment; if (pageNum >= 1 && pageNum <= pageData.pages) { field.setValue(pageNum); } } } }, // private beforeLoad : function(){ if(this.rendered && this.refresh){ this.refresh.disable(); } }, // private doLoad : function(start){ if(this.fireEvent('beforechange', this, o) !== false){ this.store.load(); } }, /** * Move to the first page, has the same effect as clicking the 'first' button. */ moveFirst : function(){ if (this.fireEvent('beforechange', this, 1) !== false){ this.store.loadPage(1); } }, /** * Move to the previous page, has the same effect as clicking the 'previous' button. */ movePrevious : function(){ var me = this, prev = me.store.currentPage - 1; if (prev > 0) { if (me.fireEvent('beforechange', me, prev) !== false) { me.store.previousPage(); } } }, /** * Move to the next page, has the same effect as clicking the 'next' button. */ moveNext : function(){ var me = this, total = me.getPageData().pageCount, next = me.store.currentPage + 1; if (next <= total) { if (me.fireEvent('beforechange', me, next) !== false) { me.store.nextPage(); } } }, /** * Move to the last page, has the same effect as clicking the 'last' button. */ moveLast : function(){ var me = this, last = me.getPageData().pageCount; if (me.fireEvent('beforechange', me, last) !== false) { me.store.loadPage(last); } }, /** * Refresh the current page, has the same effect as clicking the 'refresh' button. */ doRefresh : function(){ var me = this, current = me.store.currentPage; if (me.fireEvent('beforechange', me, current) !== false) { me.store.loadPage(current); } }, /** * Binds the paging toolbar to the specified {@link Ext.data.Store} * @param {Ext.data.Store} store The store to bind to this toolbar * @param {Boolean} initial (Optional) true to not remove listeners */ bindStore : function(store, initial){ var me = this; if (!initial && me.store) { if(store !== me.store && me.store.autoDestroy){ me.store.destroyStore(); }else{ me.store.un('beforeload', me.beforeLoad, me); me.store.un('load', me.onLoad, me); me.store.un('exception', me.onLoadError, me); } if(!store){ me.store = null; } } if (store) { store = Ext.data.StoreManager.lookup(store); store.on({ scope: me, beforeload: me.beforeLoad, load: me.onLoad, exception: me.onLoadError }); } me.store = store; }, /** * Unbinds the paging toolbar from the specified {@link Ext.data.Store} **(deprecated)** * @param {Ext.data.Store} store The data store to unbind */ unbind : function(store){ this.bindStore(null); }, /** * Binds the paging toolbar to the specified {@link Ext.data.Store} **(deprecated)** * @param {Ext.data.Store} store The data store to bind */ bind : function(store){ this.bindStore(store); }, // private onDestroy : function(){ this.bindStore(null); this.callParent(); } });