X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/toolbar/Paging.js diff --git a/src/toolbar/Paging.js b/src/toolbar/Paging.js new file mode 100644 index 00000000..07f5713f --- /dev/null +++ b/src/toolbar/Paging.js @@ -0,0 +1,576 @@ +/** + * @class Ext.toolbar.Paging + * @extends Ext.toolbar.Toolbar + *

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.

+ *

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 {@link Ext.data.Store#paramNames paramNames} used for + * paging criteria.

+ * + * {@img Ext.toolbar.Paging/Ext.toolbar.Paging.png Ext.toolbar.Paging component} + * + *

PagingToolbar is typically used as one of the Grid's toolbars:

+ *

+ *    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 = new 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:

+ *
+ * @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 + * true to display the displayMsg (defaults to false) + */ + displayInfo: false, + /** + * @cfg {Boolean} prependButtons + * true to insert any configured items before the paging buttons. + * Defaults to false. + */ + prependButtons: false, + /** + * @cfg {String} displayMsg + * The paging status message to display (defaults to 'Displaying {0} - {1} of {2}'). + * 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 (defaults to 'No data to display') + */ + emptyMsg : 'No data to display', + /** + * @cfg {String} beforePageText + * The text displayed before the input item (defaults to 'Page'). + */ + beforePageText : 'Page', + /** + * @cfg {String} afterPageText + * Customizable piece of the default paging text (defaults to 'of {0}'). 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 (defaults to 'First Page'). + * 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 (defaults to 'Previous Page'). + * 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 (defaults to 'Next Page'). + * 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 (defaults to 'Last Page'). + * 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 (defaults to 'Refresh'). + * 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 (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: + */ + '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} (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(); + } +});