commit extjs-2.2.1
[extjs.git] / source / widgets / PagingToolbar.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10  * @class Ext.PagingToolbar\r
11  * @extends Ext.Toolbar\r
12  * <p>A specialized toolbar that is bound to a {@link Ext.data.Store} and provides automatic paging control. This\r
13  * Component {@link Ext.data.Store#load load}s blocks of data into the Store passing parameters who's names are\r
14  * specified by the store's {@link Ext.data.Store#paramNames paramNames} property.</p>\r
15  * @constructor\r
16  * Create a new PagingToolbar\r
17  * @param {Object} config The config object\r
18  */\r
19 Ext.PagingToolbar = Ext.extend(Ext.Toolbar, {\r
20     /**\r
21      * @cfg {Ext.data.Store} store The {@link Ext.data.Store} the paging toolbar should use as its data source (required).\r
22      */\r
23     /**\r
24      * @cfg {Boolean} displayInfo\r
25      * True to display the displayMsg (defaults to false)\r
26      */\r
27     /**\r
28      * @cfg {Number} pageSize\r
29      * The number of records to display per page (defaults to 20)\r
30      */\r
31     pageSize: 20,\r
32     /**\r
33      * @cfg {String} displayMsg\r
34      * The paging status message to display (defaults to "Displaying {0} - {1} of {2}").  Note that this string is\r
35      * formatted using the braced numbers 0-2 as tokens that are replaced by the values for start, end and total\r
36      * respectively. These tokens should be preserved when overriding this string if showing those values is desired.\r
37      */\r
38     displayMsg : 'Displaying {0} - {1} of {2}',\r
39     /**\r
40      * @cfg {String} emptyMsg\r
41      * The message to display when no records are found (defaults to "No data to display")\r
42      */\r
43     emptyMsg : 'No data to display',\r
44     /**\r
45      * Customizable piece of the default paging text (defaults to "Page")\r
46      * @type String\r
47      */\r
48     beforePageText : "Page",\r
49     /**\r
50      * Customizable piece of the default paging text (defaults to "of {0}"). Note that this string is\r
51      * formatted using {0} as a token that is replaced by the number of total pages. This token should be\r
52      * preserved when overriding this string if showing the total page count is desired.\r
53      * @type String\r
54      */\r
55     afterPageText : "of {0}",\r
56     /**\r
57      * Customizable piece of the default paging text (defaults to "First Page")\r
58      * @type String\r
59      */\r
60     firstText : "First Page",\r
61     /**\r
62      * Customizable piece of the default paging text (defaults to "Previous Page")\r
63      * @type String\r
64      */\r
65     prevText : "Previous Page",\r
66     /**\r
67      * Customizable piece of the default paging text (defaults to "Next Page")\r
68      * @type String\r
69      */\r
70     nextText : "Next Page",\r
71     /**\r
72      * Customizable piece of the default paging text (defaults to "Last Page")\r
73      * @type String\r
74      */\r
75     lastText : "Last Page",\r
76     /**\r
77      * Customizable piece of the default paging text (defaults to "Refresh")\r
78      * @type String\r
79      */\r
80     refreshText : "Refresh",\r
81 \r
82     /**\r
83      * Object mapping of parameter names for load calls (defaults to {start: 'start', limit: 'limit'})\r
84      */\r
85     paramNames : {start: 'start', limit: 'limit'},\r
86 \r
87     // private\r
88     initComponent : function(){\r
89         this.addEvents(\r
90             /**\r
91              * @event change\r
92              * Fires after the active page has been changed.\r
93              * @param {Ext.PagingToolbar} this\r
94              * @param {Object} changeEvent An object that has these properties:<ul>\r
95              * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as\r
96              * returned by the server</div></li>\r
97              * <li><code>activePage</code> : Number <div class="sub-desc">The current page number</div></li>\r
98              * <li><code>pages</code> : Number <div class="sub-desc">The total number of pages (calculated from\r
99              * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>\r
100              * </ul>\r
101              */\r
102             'change',\r
103             /**\r
104              * @event beforechange\r
105              * Fires just before the active page is changed.\r
106              * Return false to prevent the active page from being changed.\r
107              * @param {Ext.PagingToolbar} this\r
108              * @param {Object} beforeChangeEvent An object that has these properties:<ul>\r
109              * <li><code>start</code> : Number <div class="sub-desc">The starting row number for the next page of records to\r
110              * be retrieved from the server</div></li>\r
111              * <li><code>limit</code> : Number <div class="sub-desc">The number of records to be retrieved from the server</div></li>\r
112              * </ul>\r
113              * (note: the names of the <b>start</b> and <b>limit</b> properties are determined\r
114              * by the store's {@link Ext.data.Store#paramNames paramNames} property.)\r
115              */\r
116             'beforechange'\r
117         );\r
118         Ext.PagingToolbar.superclass.initComponent.call(this);\r
119         this.cursor = 0;\r
120         this.bind(this.store);\r
121     },\r
122 \r
123     // private\r
124     onRender : function(ct, position){\r
125         Ext.PagingToolbar.superclass.onRender.call(this, ct, position);\r
126         this.first = this.addButton({\r
127             tooltip: this.firstText,\r
128             iconCls: "x-tbar-page-first",\r
129             disabled: true,\r
130             handler: this.onClick.createDelegate(this, ["first"])\r
131         });\r
132         this.prev = this.addButton({\r
133             tooltip: this.prevText,\r
134             iconCls: "x-tbar-page-prev",\r
135             disabled: true,\r
136             handler: this.onClick.createDelegate(this, ["prev"])\r
137         });\r
138         this.addSeparator();\r
139         this.add(this.beforePageText);\r
140         this.field = Ext.get(this.addDom({\r
141            tag: "input",\r
142            type: "text",\r
143            size: "3",\r
144            value: "1",\r
145            cls: "x-tbar-page-number"\r
146         }).el);\r
147         this.field.on("keydown", this.onPagingKeydown, this);\r
148         this.field.on("focus", function(){this.dom.select();});\r
149         this.field.on("blur", this.onPagingBlur, this);\r
150         this.afterTextEl = this.addText(String.format(this.afterPageText, 1));\r
151         this.field.setHeight(18);\r
152         this.addSeparator();\r
153         this.next = this.addButton({\r
154             tooltip: this.nextText,\r
155             iconCls: "x-tbar-page-next",\r
156             disabled: true,\r
157             handler: this.onClick.createDelegate(this, ["next"])\r
158         });\r
159         this.last = this.addButton({\r
160             tooltip: this.lastText,\r
161             iconCls: "x-tbar-page-last",\r
162             disabled: true,\r
163             handler: this.onClick.createDelegate(this, ["last"])\r
164         });\r
165         this.addSeparator();\r
166         this.loading = this.addButton({\r
167             tooltip: this.refreshText,\r
168             iconCls: "x-tbar-loading",\r
169             handler: this.onClick.createDelegate(this, ["refresh"])\r
170         });\r
171 \r
172         if(this.displayInfo){\r
173             this.displayEl = Ext.fly(this.el.dom).createChild({cls:'x-paging-info'});\r
174         }\r
175         if(this.dsLoaded){\r
176             this.onLoad.apply(this, this.dsLoaded);\r
177         }\r
178     },\r
179 \r
180     // private\r
181     updateInfo : function(){\r
182         if(this.displayEl){\r
183             var count = this.store.getCount();\r
184             var msg = count == 0 ?\r
185                 this.emptyMsg :\r
186                 String.format(\r
187                     this.displayMsg,\r
188                     this.cursor+1, this.cursor+count, this.store.getTotalCount()\r
189                 );\r
190             this.displayEl.update(msg);\r
191         }\r
192     },\r
193 \r
194     // private\r
195     onLoad : function(store, r, o){\r
196         if(!this.rendered){\r
197             this.dsLoaded = [store, r, o];\r
198             return;\r
199         }\r
200        this.cursor = o.params ? o.params[this.paramNames.start] : 0;\r
201        var d = this.getPageData(), ap = d.activePage, ps = d.pages;\r
202 \r
203         this.afterTextEl.el.innerHTML = String.format(this.afterPageText, d.pages);\r
204         this.field.dom.value = ap;\r
205         this.first.setDisabled(ap == 1);\r
206         this.prev.setDisabled(ap == 1);\r
207         this.next.setDisabled(ap == ps);\r
208         this.last.setDisabled(ap == ps);\r
209         this.loading.enable();\r
210         this.updateInfo();\r
211         this.fireEvent('change', this, d);\r
212     },\r
213 \r
214     // private\r
215     getPageData : function(){\r
216         var total = this.store.getTotalCount();\r
217         return {\r
218             total : total,\r
219             activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize),\r
220             pages :  total < this.pageSize ? 1 : Math.ceil(total/this.pageSize)\r
221         };\r
222     },\r
223 \r
224     // private\r
225     onLoadError : function(){\r
226         if(!this.rendered){\r
227             return;\r
228         }\r
229         this.loading.enable();\r
230     },\r
231 \r
232     // private\r
233     readPage : function(d){\r
234         var v = this.field.dom.value, pageNum;\r
235         if (!v || isNaN(pageNum = parseInt(v, 10))) {\r
236             this.field.dom.value = d.activePage;\r
237             return false;\r
238         }\r
239         return pageNum;\r
240     },\r
241 \r
242     //private\r
243     onPagingBlur: function(e){\r
244         this.field.dom.value = this.getPageData().activePage;\r
245     },\r
246 \r
247     // private\r
248     onPagingKeydown : function(e){\r
249         var k = e.getKey(), d = this.getPageData(), pageNum;\r
250         if (k == e.RETURN) {\r
251             e.stopEvent();\r
252             pageNum = this.readPage(d);\r
253             if(pageNum !== false){\r
254                 pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1;\r
255                 this.doLoad(pageNum * this.pageSize);\r
256             }\r
257         }else if (k == e.HOME || k == e.END){\r
258             e.stopEvent();\r
259             pageNum = k == e.HOME ? 1 : d.pages;\r
260             this.field.dom.value = pageNum;\r
261         }else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN){\r
262             e.stopEvent();\r
263             if(pageNum = this.readPage(d)){\r
264                 var increment = e.shiftKey ? 10 : 1;\r
265                 if(k == e.DOWN || k == e.PAGEDOWN){\r
266                     increment *= -1;\r
267                 }\r
268                 pageNum += increment;\r
269                 if(pageNum >= 1 & pageNum <= d.pages){\r
270                     this.field.dom.value = pageNum;\r
271                 }\r
272             }\r
273         }\r
274     },\r
275 \r
276     // private\r
277     beforeLoad : function(){\r
278         if(this.rendered && this.loading){\r
279             this.loading.disable();\r
280         }\r
281     },\r
282 \r
283     // private\r
284     doLoad : function(start){\r
285         var o = {}, pn = this.paramNames;\r
286         o[pn.start] = start;\r
287         o[pn.limit] = this.pageSize;\r
288         if(this.fireEvent('beforechange', this, o) !== false){\r
289             this.store.load({params:o});\r
290         }\r
291     },\r
292 \r
293     /**\r
294      * Change the active page\r
295      * @param {Integer} page The page to display\r
296      */\r
297     changePage: function(page){\r
298         this.doLoad(((page-1) * this.pageSize).constrain(0, this.store.getTotalCount()));\r
299     },\r
300 \r
301     // private\r
302     onClick : function(which){\r
303         var store = this.store;\r
304         switch(which){\r
305             case "first":\r
306                 this.doLoad(0);\r
307             break;\r
308             case "prev":\r
309                 this.doLoad(Math.max(0, this.cursor-this.pageSize));\r
310             break;\r
311             case "next":\r
312                 this.doLoad(this.cursor+this.pageSize);\r
313             break;\r
314             case "last":\r
315                 var total = store.getTotalCount();\r
316                 var extra = total % this.pageSize;\r
317                 var lastStart = extra ? (total - extra) : total-this.pageSize;\r
318                 this.doLoad(lastStart);\r
319             break;\r
320             case "refresh":\r
321                 this.doLoad(this.cursor);\r
322             break;\r
323         }\r
324     },\r
325 \r
326     /**\r
327      * Unbinds the paging toolbar from the specified {@link Ext.data.Store}\r
328      * @param {Ext.data.Store} store The data store to unbind\r
329      */\r
330     unbind : function(store){\r
331         store = Ext.StoreMgr.lookup(store);\r
332         store.un("beforeload", this.beforeLoad, this);\r
333         store.un("load", this.onLoad, this);\r
334         store.un("loadexception", this.onLoadError, this);\r
335         this.store = undefined;\r
336     },\r
337 \r
338     /**\r
339      * Binds the paging toolbar to the specified {@link Ext.data.Store}\r
340      * @param {Ext.data.Store} store The data store to bind\r
341      */\r
342     bind : function(store){\r
343         store = Ext.StoreMgr.lookup(store);\r
344         store.on("beforeload", this.beforeLoad, this);\r
345         store.on("load", this.onLoad, this);\r
346         store.on("loadexception", this.onLoadError, this);\r
347         this.store = store;\r
348     },\r
349 \r
350     // private\r
351     onDestroy : function(){\r
352         if(this.store){\r
353             this.unbind(this.store);\r
354         }\r
355         Ext.PagingToolbar.superclass.onDestroy.call(this);\r
356     }\r
357 });\r
358 Ext.reg('paging', Ext.PagingToolbar);