/** * @class Ext.toolbar.Paging * @extends Ext.toolbar.Toolbar * <p>As the amount 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.</p> * <p>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.</p> * <p><b>Ext.toolbar.Paging</b> 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 <tt>{@link #store}</tt> by passing {@link Ext.data.Store#paramNames paramNames} used for * paging criteria.</p> * * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component} * * <p>PagingToolbar is typically used as one of the Grid's toolbars:</p> * <pre><code> * 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() * }); * </code></pre> * * <p>To use paging, pass the paging requirements to the server when the store is first loaded.</p> * <pre><code> store.load({ params: { // specify params for the first page load if using paging start: 0, limit: myPageSize, // other params foo: 'bar' } }); * </code></pre> * * <p>If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:</p> * <pre><code> var myStore = new Ext.data.Store({ {@link Ext.data.Store#autoLoad autoLoad}: {start: 0, limit: 25}, ... }); * </code></pre> * * <p>The packet sent back from the server would have this form:</p> * <pre><code> { "success": true, "results": 2000, "rows": [ // <b>*Note:</b> this must be an Array { "id": 1, "name": "Bill", "occupation": "Gardener" }, { "id": 2, "name": "Ben", "occupation": "Horticulturalist" }, ... { "id": 25, "name": "Sue", "occupation": "Botanist" } ] } * </code></pre> * <p><u>Paging with Local Data</u></p> * <p>Paging can also be accomplished with local data using extensions:</p> * <div class="mdetail-params"><ul> * <li><a href="http://sencha.com/forum/showthread.php?t=71532">Ext.ux.data.PagingStore</a></li> * <li>Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)</li> * </ul></div> * @constructor Create a new PagingToolbar * @param {Object} config The config object * @xtype pagingtoolbar */ 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 * The {@link Ext.data.Store} the paging toolbar should use as its data source (required). */ /** * @cfg {Boolean} displayInfo * <tt>true</tt> to display the displayMsg (defaults to <tt>false</tt>) */ displayInfo: false, /** * @cfg {Boolean} prependButtons * <tt>true</tt> to insert any configured <tt>items</tt> <i>before</i> the paging buttons. * Defaults to <tt>false</tt>. */ prependButtons: false, /** * @cfg {String} displayMsg * The paging status message to display (defaults to <tt>'Displaying {0} - {1} of {2}'</tt>). * Note that this string is formatted using the braced numbers <tt>{0}-{2}</tt> 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 (defaults to 'No data to display') */ emptyMsg : 'No data to display', /** * @cfg {String} beforePageText * The text displayed before the input item (defaults to <tt>'Page'</tt>). */ beforePageText : 'Page', /** * @cfg {String} afterPageText * Customizable piece of the default paging text (defaults to <tt>'of {0}'</tt>). Note that * this string is formatted using <tt>{0}</tt> 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 (defaults to <tt>'First Page'</tt>). * <b>Note</b>: 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 (defaults to <tt>'Previous Page'</tt>). * <b>Note</b>: 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 (defaults to <tt>'Next Page'</tt>). * <b>Note</b>: 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 (defaults to <tt>'Last Page'</tt>). * <b>Note</b>: quick tips must be initialized for the quicktip to show. */ lastText : 'Last Page', /** * @cfg {String} refreshText * The quicktip text displayed for the Refresh button (defaults to <tt>'Refresh'</tt>). * <b>Note</b>: 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 (defaults to 30). */ 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:<ul> * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as * returned by the server</div></li> * <li><code>currentPage</code> : Number <div class="sub-desc">The current page number</div></li> * <li><code>pageCount</code> : Number <div class="sub-desc">The total number of pages (calculated from * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li> * <li><code>toRecord</code> : Number <div class="sub-desc">The starting record index for the current page</div></li> * <li><code>fromRecord</code> : Number <div class="sub-desc">The ending record index for the current page</div></li> * </ul> */ '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, 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), //pageCount : store.getPageCount(), 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 k = e.getKey(), pageData = this.getPageData(), increment = e.shiftKey ? 10 : 1, pageNum, me = this; if (k == e.RETURN) { e.stopEvent(); pageNum = me.readPageFromInput(pageData); if (pageNum !== false) { pageNum = Math.min(Math.max(1, pageNum), pageData.total); 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(){ var me = this; if(me.fireEvent('beforechange', me, 1) !== false){ me.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(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; if(me.fireEvent('beforechange', me, me.store.currentPage + 1) !== false){ me.store.nextPage(); } }, /** * Move to the last page, has the same effect as clicking the 'last' button. */ moveLast : function(){ var me = this, last = this.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 {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.destroy(); }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} <b>(deprecated)</b> * @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} <b>(deprecated)</b> * @param {Ext.data.Store} store The data store to bind */ bind : function(store){ this.bindStore(store); }, // private onDestroy : function(){ this.bindStore(null); this.callParent(); } });