X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/ee06f37b0f6f6d94cd05a6ffae556660f7c4a2bc..c930e9176a5a85509c5b0230e2bff5c22a591432:/pkgs/pkg-toolbars-debug.js diff --git a/pkgs/pkg-toolbars-debug.js b/pkgs/pkg-toolbars-debug.js new file mode 100644 index 00000000..5daf84c5 --- /dev/null +++ b/pkgs/pkg-toolbars-debug.js @@ -0,0 +1,1374 @@ +/*! + * Ext JS Library 3.0.0 + * Copyright(c) 2006-2009 Ext JS, LLC + * licensing@extjs.com + * http://www.extjs.com/license + */ +/** + * @class Ext.layout.ToolbarLayout + * @extends Ext.layout.ContainerLayout + * Layout manager implicitly used by Ext.Toolbar. + */ +Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { + monitorResize : true, + triggerWidth : 18, + lastOverflow : false, + + noItemsMenuText : '
(None)
', + // private + onLayout : function(ct, target){ + if(!this.leftTr){ + target.addClass('x-toolbar-layout-ct'); + target.insertHtml('beforeEnd', + '
'); + this.leftTr = target.child('tr.x-toolbar-left-row', true); + this.rightTr = target.child('tr.x-toolbar-right-row', true); + this.extrasTr = target.child('tr.x-toolbar-extras-row', true); + } + var side = this.leftTr; + var pos = 0; + + var items = ct.items.items; + for(var i = 0, len = items.length, c; i < len; i++, pos++) { + c = items[i]; + if(c.isFill){ + side = this.rightTr; + pos = -1; + }else if(!c.rendered){ + c.render(this.insertCell(c, side, pos)); + }else{ + if(!c.xtbHidden && !this.isValidParent(c, side.childNodes[pos])){ + var td = this.insertCell(c, side, pos); + td.appendChild(c.getDomPositionEl().dom); + c.container = Ext.get(td); + } + } + } + //strip extra empty cells + this.cleanup(this.leftTr); + this.cleanup(this.rightTr); + this.cleanup(this.extrasTr); + this.fitToSize(target); + }, + + cleanup : function(row){ + var cn = row.childNodes; + for(var i = cn.length-1, c; i >= 0 && (c = cn[i]); i--){ + if(!c.firstChild){ + row.removeChild(c); + } + } + }, + + insertCell : function(c, side, pos){ + var td = document.createElement('td'); + td.className='x-toolbar-cell'; + side.insertBefore(td, side.childNodes[pos]||null); + return td; + }, + + hideItem : function(item){ + var h = (this.hiddens = this.hiddens || []); + h.push(item); + item.xtbHidden = true; + item.xtbWidth = item.getDomPositionEl().dom.parentNode.offsetWidth; + item.hide(); + }, + + unhideItem : function(item){ + item.show(); + item.xtbHidden = false; + this.hiddens.remove(item); + if(this.hiddens.length < 1){ + delete this.hiddens; + } + }, + + getItemWidth : function(c){ + return c.hidden ? (c.xtbWidth || 0) : c.getDomPositionEl().dom.parentNode.offsetWidth; + }, + + fitToSize : function(t){ + if(this.container.enableOverflow === false){ + return; + } + var w = t.dom.clientWidth; + var lw = this.lastWidth || 0; + this.lastWidth = w; + var iw = t.dom.firstChild.offsetWidth; + + var clipWidth = w - this.triggerWidth; + var hideIndex = -1; + + if(iw > w || (this.hiddens && w >= lw)){ + var i, items = this.container.items.items, len = items.length, c; + var loopWidth = 0; + for(i = 0; i < len; i++) { + c = items[i]; + if(!c.isFill){ + loopWidth += this.getItemWidth(c); + if(loopWidth > clipWidth){ + if(!c.xtbHidden){ + this.hideItem(c); + } + }else{ + if(c.xtbHidden){ + this.unhideItem(c); + } + } + } + } + } + if(this.hiddens){ + this.initMore(); + if(!this.lastOverflow){ + this.container.fireEvent('overflowchange', this.container, true); + this.lastOverflow = true; + } + }else if(this.more){ + this.clearMenu(); + this.more.destroy(); + delete this.more; + if(this.lastOverflow){ + this.container.fireEvent('overflowchange', this.container, false); + this.lastOverflow = false; + } + } + }, + + createMenuConfig : function(c, hideOnClick){ + var cfg = Ext.apply({}, c.initialConfig), + group = c.toggleGroup; + + Ext.apply(cfg, { + text: c.overflowText || c.text, + iconCls: c.iconCls, + icon: c.icon, + itemId: c.itemId, + disabled: c.disabled, + handler: c.handler, + scope: c.scope, + menu: c.menu, + hideOnClick: hideOnClick + }); + if(group || c.enableToggle){ + Ext.apply(cfg, { + group: group, + checked: c.pressed, + listeners: { + checkchange: function(item, checked){ + c.toggle(checked); + } + } + }); + } + delete cfg.xtype; + delete cfg.id; + return cfg; + }, + + // private + addComponentToMenu : function(m, c){ + if(c instanceof Ext.Toolbar.Separator){ + m.add('-'); + }else if(Ext.isFunction(c.isXType)){ + if(c.isXType('splitbutton')){ + m.add(this.createMenuConfig(c, true)); + }else if(c.isXType('button')){ + m.add(this.createMenuConfig(c, !c.menu)); + }else if(c.isXType('buttongroup')){ + c.items.each(function(item){ + this.addComponentToMenu(m, item); + }, this); + } + } + }, + + clearMenu : function(){ + var m = this.moreMenu; + if(m && m.items){ + this.moreMenu.items.each(function(item){ + delete item.menu; + }); + } + }, + + // private + beforeMoreShow : function(m){ + var h = this.container.items.items, + len = h.length, + c, + prev, + needsSep = function(group, item){ + return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator); + }; + + this.clearMenu(); + m.removeAll(); + for(var i = 0; i < len; i++){ + c = h[i]; + if(c.xtbHidden){ + if(prev && (needsSep(c, prev) || needsSep(prev, c))){ + m.add('-'); + } + this.addComponentToMenu(m, c); + prev = c; + } + } + // put something so the menu isn't empty + // if no compatible items found + if(m.items.length < 1){ + m.add(this.noItemsMenuText); + } + }, + + initMore : function(){ + if(!this.more){ + this.moreMenu = new Ext.menu.Menu({ + listeners: { + beforeshow: this.beforeMoreShow, + scope: this + } + }); + this.more = new Ext.Button({ + iconCls: 'x-toolbar-more-icon', + cls: 'x-toolbar-more', + menu: this.moreMenu + }); + var td = this.insertCell(this.more, this.extrasTr, 100); + this.more.render(td); + } + }, + + destroy : function(){ + Ext.destroy(this.more, this.moreMenu); + Ext.layout.ToolbarLayout.superclass.destroy.call(this); + } + /** + * @property activeItem + * @hide + */ +}); + +Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout; + +/** + * @class Ext.Toolbar + * @extends Ext.Container + *

Basic Toolbar class. Although the {@link Ext.Container#defaultType defaultType} for Toolbar + * is {@link Ext.Button button}, Toolbar elements (child items for the Toolbar container) may + * be virtually any type of Component. Toolbar elements can be created explicitly via their constructors, + * or implicitly via their xtypes, and can be {@link #add}ed dynamically.

+ *

Some items have shortcut strings for creation:

+ *
+Shortcut  xtype          Class                  Description
+'->'      'tbfill'       {@link Ext.Toolbar.Fill}       begin using the right-justified button container
+'-'       'tbseparator'  {@link Ext.Toolbar.Separator}  add a vertical separator bar between toolbar items
+' '       'tbspacer'     {@link Ext.Toolbar.Spacer}     add horiztonal space between elements
+ * 
+ * + * Example usage of various elements: + *

+var tb = new Ext.Toolbar({
+    renderTo: document.body,
+    width: 600,
+    height: 100,
+    items: [
+        {
+            // xtype: 'button', // default for Toolbars, same as 'tbbutton'
+            text: 'Button'
+        },
+        {
+            xtype: 'splitbutton', // same as 'tbsplitbutton'
+            text: 'Split Button'
+        },
+        // begin using the right-justified button container
+        '->', // same as {xtype: 'tbfill'}, // Ext.Toolbar.Fill
+        {
+            xtype: 'textfield',
+            name: 'field1',
+            emptyText: 'enter search term'
+        },
+        // add a vertical separator bar between toolbar items
+        '-', // same as {xtype: 'tbseparator'} to create Ext.Toolbar.Separator
+        'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.Toolbar.TextItem
+        {xtype: 'tbspacer'},// same as ' ' to create Ext.Toolbar.Spacer
+        'text 2',
+        {xtype: 'tbspacer', width: 50}, // add a 50px space
+        'text 3'
+    ]
+});
+ * 
+ * Example adding a ComboBox within a menu of a button: + *

+// ComboBox creation
+var combo = new Ext.form.ComboBox({
+    store: new Ext.data.ArrayStore({
+        autoDestroy: true,
+        fields: ['initials', 'fullname'],
+        data : [
+            ['FF', 'Fred Flintstone'],
+            ['BR', 'Barney Rubble']
+        ]
+    }),
+    displayField: 'fullname',
+    typeAhead: true,
+    mode: 'local',
+    forceSelection: true,
+    triggerAction: 'all',
+    emptyText: 'Select a name...',
+    selectOnFocus: true,
+    width: 135,
+    getListParent: function() {
+        return this.el.up('.x-menu');
+    },
+    iconCls: 'no-icon' //use iconCls if placing within menu to shift to right side of menu
+});
+
+// put ComboBox in a Menu
+var menu = new Ext.menu.Menu({
+    id: 'mainMenu',
+    items: [
+        combo // A Field in a Menu
+    ]
+});
+
+// add a Button with the menu
+tb.add({
+        text:'Button w/ Menu',
+        menu: menu  // assign menu by instance
+    });
+tb.doLayout();
+ * 
+ * @constructor + * Creates a new Toolbar + * @param {Object/Array} config A config object or an array of buttons to {@link #add} + * @xtype toolbar + */ +Ext.Toolbar = function(config){ + if(Ext.isArray(config)){ + config = {items: config, layout: 'toolbar'}; + } else { + config = Ext.apply({ + layout: 'toolbar' + }, config); + if(config.buttons) { + config.items = config.buttons; + } + } + Ext.Toolbar.superclass.constructor.call(this, config); +}; + +(function(){ + +var T = Ext.Toolbar; + +Ext.extend(T, Ext.Container, { + + defaultType: 'button', + + trackMenus : true, + internalDefaults: {removeMode: 'container', hideParent: true}, + toolbarCls: 'x-toolbar', + + initComponent : function(){ + T.superclass.initComponent.call(this); + + /** + * @event overflowchange + * Fires after the overflow state has changed. + * @param {Object} c The Container + * @param {Boolean} lastOverflow overflow state + */ + this.addEvents('overflowchange'); + }, + + // private + onRender : function(ct, position){ + if(!this.el){ + if(!this.autoCreate){ + this.autoCreate = { + cls: this.toolbarCls + ' x-small-editor' + }; + } + this.el = ct.createChild(Ext.apply({ id: this.id },this.autoCreate), position); + } + }, + + /** + * Adds element(s) to the toolbar -- this function takes a variable number of + * arguments of mixed type and adds them to the toolbar. + * @param {Mixed} arg1 The following types of arguments are all valid:
+ * + * @param {Mixed} arg2 + * @param {Mixed} etc. + * @method add + */ + + // private + lookupComponent : function(c){ + if(Ext.isString(c)){ + if(c == '-'){ + c = new T.Separator(); + }else if(c == ' '){ + c = new T.Spacer(); + }else if(c == '->'){ + c = new T.Fill(); + }else{ + c = new T.TextItem(c); + } + this.applyDefaults(c); + }else{ + if(c.isFormField || c.render){ // some kind of form field, some kind of Toolbar.Item + c = this.constructItem(c); + }else if(c.tag){ // DomHelper spec + c = new T.Item({autoEl: c}); + }else if(c.tagName){ // element + c = new T.Item({el:c}); + }else if(Ext.isObject(c)){ // must be button config? + c = c.xtype ? this.constructItem(c) : this.constructButton(c); + } + } + return c; + }, + + // private + applyDefaults : function(c){ + if(!Ext.isString(c)){ + c = Ext.Toolbar.superclass.applyDefaults.call(this, c); + var d = this.internalDefaults; + if(c.events){ + Ext.applyIf(c.initialConfig, d); + Ext.apply(c, d); + }else{ + Ext.applyIf(c, d); + } + } + return c; + }, + + // private + constructItem : function(item, type){ + return Ext.create(item, type || this.defaultType); + }, + + /** + * Adds a separator + * @return {Ext.Toolbar.Item} The separator {@link Ext.Toolbar.Item item} + */ + addSeparator : function(){ + return this.add(new T.Separator()); + }, + + /** + * Adds a spacer element + * @return {Ext.Toolbar.Spacer} The spacer item + */ + addSpacer : function(){ + return this.add(new T.Spacer()); + }, + + /** + * Forces subsequent additions into the float:right toolbar + */ + addFill : function(){ + this.add(new T.Fill()); + }, + + /** + * Adds any standard HTML element to the toolbar + * @param {Mixed} el The element or id of the element to add + * @return {Ext.Toolbar.Item} The element's item + */ + addElement : function(el){ + return this.addItem(new T.Item({el:el})); + }, + + /** + * Adds any Toolbar.Item or subclass + * @param {Ext.Toolbar.Item} item + * @return {Ext.Toolbar.Item} The item + */ + addItem : function(item){ + return Ext.Toolbar.superclass.add.apply(this, arguments); + }, + + /** + * Adds a button (or buttons). See {@link Ext.Button} for more info on the config. + * @param {Object/Array} config A button config or array of configs + * @return {Ext.Button/Array} + */ + addButton : function(config){ + if(Ext.isArray(config)){ + var buttons = []; + for(var i = 0, len = config.length; i < len; i++) { + buttons.push(this.addButton(config[i])); + } + return buttons; + } + return this.add(this.constructButton(config)); + }, + + /** + * Adds text to the toolbar + * @param {String} text The text to add + * @return {Ext.Toolbar.Item} The element's item + */ + addText : function(text){ + return this.addItem(new T.TextItem(text)); + }, + + /** + * Adds a new element to the toolbar from the passed {@link Ext.DomHelper} config + * @param {Object} config + * @return {Ext.Toolbar.Item} The element's item + */ + addDom : function(config){ + return this.add(new T.Item({autoEl: config})); + }, + + /** + * Adds a dynamically rendered Ext.form field (TextField, ComboBox, etc). Note: the field should not have + * been rendered yet. For a field that has already been rendered, use {@link #addElement}. + * @param {Ext.form.Field} field + * @return {Ext.Toolbar.Item} + */ + addField : function(field){ + return this.add(field); + }, + + /** + * Inserts any {@link Ext.Toolbar.Item}/{@link Ext.Button} at the specified index. + * @param {Number} index The index where the item is to be inserted + * @param {Object/Ext.Toolbar.Item/Ext.Button/Array} item The button, or button config object to be + * inserted, or an array of buttons/configs. + * @return {Ext.Button/Item} + */ + insertButton : function(index, item){ + if(Ext.isArray(item)){ + var buttons = []; + for(var i = 0, len = item.length; i < len; i++) { + buttons.push(this.insertButton(index + i, item[i])); + } + return buttons; + } + return Ext.Toolbar.superclass.insert.call(this, index, item); + }, + + // private + initMenuTracking : function(item){ + if(this.trackMenus && item.menu){ + this.mon(item, { + 'menutriggerover' : this.onButtonTriggerOver, + 'menushow' : this.onButtonMenuShow, + 'menuhide' : this.onButtonMenuHide, + scope: this + }); + } + }, + + // private + constructButton : function(item){ + var b = item.events ? item : this.constructItem(item, item.split ? 'splitbutton' : this.defaultType); + this.initMenuTracking(b); + return b; + }, + + // private + onDisable : function(){ + this.items.each(function(item){ + if(item.disable){ + item.disable(); + } + }); + }, + + // private + onEnable : function(){ + this.items.each(function(item){ + if(item.enable){ + item.enable(); + } + }); + }, + + // private + onButtonTriggerOver : function(btn){ + if(this.activeMenuBtn && this.activeMenuBtn != btn){ + this.activeMenuBtn.hideMenu(); + btn.showMenu(); + this.activeMenuBtn = btn; + } + }, + + // private + onButtonMenuShow : function(btn){ + this.activeMenuBtn = btn; + }, + + // private + onButtonMenuHide : function(btn){ + delete this.activeMenuBtn; + } +}); +Ext.reg('toolbar', Ext.Toolbar); + +/** + * @class Ext.Toolbar.Item + * @extends Ext.BoxComponent + * The base class that other non-interacting Toolbar Item classes should extend in order to + * get some basic common toolbar item functionality. + * @constructor + * Creates a new Item + * @param {HTMLElement} el + * @xtype tbitem + */ +T.Item = Ext.extend(Ext.BoxComponent, { + hideParent: true, // Hiding a Toolbar.Item hides its containing TD + enable:Ext.emptyFn, + disable:Ext.emptyFn, + focus:Ext.emptyFn + /** + * @cfg {String} overflowText Text to be used for the menu if the item is overflowed. + */ +}); +Ext.reg('tbitem', T.Item); + +/** + * @class Ext.Toolbar.Separator + * @extends Ext.Toolbar.Item + * A simple class that adds a vertical separator bar between toolbar items + * (css class:'xtb-sep'). Example usage: + *

+new Ext.Panel({
+    tbar : [
+        'Item 1',
+        {xtype: 'tbseparator'}, // or '-'
+        'Item 2'
+    ]
+});
+
+ * @constructor + * Creates a new Separator + * @xtype tbseparator + */ +T.Separator = Ext.extend(T.Item, { + onRender : function(ct, position){ + this.el = ct.createChild({tag:'span', cls:'xtb-sep'}, position); + } +}); +Ext.reg('tbseparator', T.Separator); + +/** + * @class Ext.Toolbar.Spacer + * @extends Ext.Toolbar.Item + * A simple element that adds extra horizontal space between items in a toolbar. + * By default a 2px wide space is added via css specification:

+.x-toolbar .xtb-spacer {
+    width:2px;
+}
+ * 
+ *

Example usage:

+ *

+new Ext.Panel({
+    tbar : [
+        'Item 1',
+        {xtype: 'tbspacer'}, // or ' '
+        'Item 2',
+        // space width is also configurable via javascript
+        {xtype: 'tbspacer', width: 50}, // add a 50px space
+        'Item 3'
+    ]
+});
+
+ * @constructor + * Creates a new Spacer + * @xtype tbspacer + */ +T.Spacer = Ext.extend(T.Item, { + /** + * @cfg {Number} width + * The width of the spacer in pixels (defaults to 2px via css style .x-toolbar .xtb-spacer). + */ + + onRender : function(ct, position){ + this.el = ct.createChild({tag:'div', cls:'xtb-spacer', style: this.width?'width:'+this.width+'px':''}, position); + } +}); +Ext.reg('tbspacer', T.Spacer); + +/** + * @class Ext.Toolbar.Fill + * @extends Ext.Toolbar.Spacer + * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using + * the right-justified button container. + *

+new Ext.Panel({
+    tbar : [
+        'Item 1',
+        {xtype: 'tbfill'}, // or '->'
+        'Item 2'
+    ]
+});
+
+ * @constructor + * Creates a new Fill + * @xtype tbfill + */ +T.Fill = Ext.extend(T.Item, { + // private + render : Ext.emptyFn, + isFill : true +}); +Ext.reg('tbfill', T.Fill); + +/** + * @class Ext.Toolbar.TextItem + * @extends Ext.Toolbar.Item + * A simple class that renders text directly into a toolbar + * (with css class:'xtb-text'). Example usage: + *

+new Ext.Panel({
+    tbar : [
+        {xtype: 'tbtext', text: 'Item 1'} // or simply 'Item 1'
+    ]
+});
+
+ * @constructor + * Creates a new TextItem + * @param {String/Object} text A text string, or a config object containing a text property + * @xtype tbtext + */ +T.TextItem = Ext.extend(T.Item, { + constructor: function(config){ + if (Ext.isString(config)) { + config = { autoEl: {cls: 'xtb-text', html: config }}; + } else { + config.autoEl = {cls: 'xtb-text', html: config.text || ''}; + } + T.TextItem.superclass.constructor.call(this, config); + }, + /** + * Updates this item's text, setting the text to be used as innerHTML. + * @param {String} t The text to display (html accepted). + */ + setText : function(t) { + if (this.rendered) { + this.el.dom.innerHTML = t; + } else { + this.autoEl.html = t; + } + } +}); +Ext.reg('tbtext', T.TextItem); + +// backwards compat +T.Button = Ext.extend(Ext.Button, {}); +T.SplitButton = Ext.extend(Ext.SplitButton, {}); +Ext.reg('tbbutton', T.Button); +Ext.reg('tbsplit', T.SplitButton); + +})(); +/** + * @class Ext.ButtonGroup + * @extends Ext.Panel + * Container for a group of buttons. Example usage: + *

+var p = new Ext.Panel({
+    title: 'Panel with Button Group',
+    width: 300,
+    height:200,
+    renderTo: document.body,
+    html: 'whatever',
+    tbar: [{
+        xtype: 'buttongroup',
+        {@link #columns}: 3,
+        title: 'Clipboard',
+        items: [{
+            text: 'Paste',
+            scale: 'large',
+            rowspan: 3, iconCls: 'add',
+            iconAlign: 'top',
+            cls: 'x-btn-as-arrow'
+        },{
+            xtype:'splitbutton',
+            text: 'Menu Button',
+            scale: 'large',
+            rowspan: 3,
+            iconCls: 'add',
+            iconAlign: 'top',
+            arrowAlign:'bottom',
+            menu: [{text: 'Menu Item 1'}]
+        },{
+            xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
+        },{
+            text: 'Copy', iconCls: 'add16'
+        },{
+            text: 'Format', iconCls: 'add16'
+        }]
+    }]
+});
+ * 
+ * @xtype buttongroup + */ +Ext.ButtonGroup = Ext.extend(Ext.Panel, { + /** + * @cfg {Number} columns The columns configuration property passed to the + * {@link #layout configured layout manager}. See {@link Ext.layout.TableLayout#columns}. + */ + /** + * @cfg {String} baseCls Defaults to 'x-btn-group'. See {@link Ext.Panel#baseCls}. + */ + baseCls: 'x-btn-group', + /** + * @cfg {String} layout Defaults to 'table'. See {@link Ext.Container#layout}. + */ + layout:'table', + defaultType: 'button', + /** + * @cfg {Boolean} frame Defaults to true. See {@link Ext.Panel#frame}. + */ + frame: true, + internalDefaults: {removeMode: 'container', hideParent: true}, + + initComponent : function(){ + this.layoutConfig = this.layoutConfig || {}; + Ext.applyIf(this.layoutConfig, { + columns : this.columns + }); + if(!this.title){ + this.addClass('x-btn-group-notitle'); + } + this.on('afterlayout', this.onAfterLayout, this); + Ext.ButtonGroup.superclass.initComponent.call(this); + }, + + applyDefaults : function(c){ + c = Ext.ButtonGroup.superclass.applyDefaults.call(this, c); + var d = this.internalDefaults; + if(c.events){ + Ext.applyIf(c.initialConfig, d); + Ext.apply(c, d); + }else{ + Ext.applyIf(c, d); + } + return c; + }, + + onAfterLayout : function(){ + var bodyWidth = this.body.getFrameWidth('lr') + this.body.dom.firstChild.offsetWidth; + this.body.setWidth(bodyWidth); + this.el.setWidth(bodyWidth + this.getFrameWidth()); + } + /** + * @cfg {Array} tools @hide + */ +}); + +Ext.reg('buttongroup', Ext.ButtonGroup); +/** + * @class Ext.PagingToolbar + * @extends Ext.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 + * approprate data.

+ *

Ext.PagingToolbar 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.

+ *

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

+ *

+Ext.QuickTips.init(); // to display button quicktips
+
+var myStore = new Ext.data.Store({
+    ...
+});
+
+var myPageSize = 25;  // server script should only send back 25 items
+
+var grid = new Ext.grid.GridPanel({
+    ...
+    store: myStore,
+    bbar: new Ext.PagingToolbar({
+        {@link #store}: myStore,       // grid and PagingToolbar using same store
+        {@link #displayInfo}: true,
+        {@link #pageSize}: myPageSize,
+        {@link #prependButtons}: true,
+        items: [
+            'text 1'
+        ]
+    })
+});
+ * 
+ * + *

To use paging, pass the paging requirements to the server when the store is first loaded.

+ *

+store.load({
+    params: {
+        start: 0,          // specify params for the first page load if using paging
+        limit: myPageSize,
+        foo:   'bar'
+    }
+});
+ * 
+ *

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 paging + */ +(function() { + +var T = Ext.Toolbar; + +Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { + /** + * @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) + */ + /** + * @cfg {Number} pageSize + * The number of records to display per page (defaults to 20) + */ + pageSize : 20, + /** + * @cfg {Boolean} prependButtons + * true to insert any configured items before the paging buttons. + * Defaults to 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', + + /** + * @deprecated + * The defaults for these should be set in the data store. + * Object mapping of parameter names used for load calls, initially set to: + *
{start: 'start', limit: 'limit'}
+ */ + + /** + * The number of records to display per page. See also {@link #cursor}. + * @type Number + * @property pageSize + */ + + /** + * Indicator for the record position. This property might be used to get the active page + * number for example:

+     * // t is reference to the paging toolbar instance
+     * var activePage = Math.ceil((t.cursor + t.pageSize) / t.pageSize);
+     * 
+ * @type Number + * @property cursor + */ + + initComponent : function(){ + var pagingItems = [this.first = new T.Button({ + tooltip: this.firstText, + overflowText: this.firstText, + iconCls: 'x-tbar-page-first', + disabled: true, + handler: this.moveFirst, + scope: this + }), this.prev = new T.Button({ + tooltip: this.prevText, + overflowText: this.prevText, + iconCls: 'x-tbar-page-prev', + disabled: true, + handler: this.movePrevious, + scope: this + }), '-', this.beforePageText, + this.inputItem = new Ext.form.NumberField({ + cls: 'x-tbar-page-number', + allowDecimals: false, + allowNegative: false, + enableKeyEvents: true, + selectOnFocus: true, + listeners: { + scope: this, + keydown: this.onPagingKeyDown, + blur: this.onPagingBlur + } + }), this.afterTextItem = new T.TextItem({ + text: String.format(this.afterPageText, 1) + }), '-', this.next = new T.Button({ + tooltip: this.nextText, + overflowText: this.nextText, + iconCls: 'x-tbar-page-next', + disabled: true, + handler: this.moveNext, + scope: this + }), this.last = new T.Button({ + tooltip: this.lastText, + overflowText: this.lastText, + iconCls: 'x-tbar-page-last', + disabled: true, + handler: this.moveLast, + scope: this + }), '-', this.refresh = new T.Button({ + tooltip: this.refreshText, + overflowText: this.refreshText, + iconCls: 'x-tbar-loading', + handler: this.refresh, + scope: this + })]; + + + var userItems = this.items || this.buttons || []; + if (this.prependButtons) { + this.items = userItems.concat(pagingItems); + }else{ + this.items = pagingItems.concat(userItems); + } + delete this.buttons; + if(this.displayInfo){ + this.items.push('->'); + this.items.push(this.displayItem = new T.TextItem({})); + } + Ext.PagingToolbar.superclass.initComponent.call(this); + this.addEvents( + /** + * @event change + * Fires after the active page has been changed. + * @param {Ext.PagingToolbar} 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.PagingToolbar} this + * @param {Object} params An object hash of the parameters which the PagingToolbar will send when + * loading the required page. This will contain: + *

(note: the names of the start and limit properties are determined + * by the store's {@link Ext.data.Store#paramNames paramNames} property.)

+ *

Parameters may be added as required in the event handler.

+ */ + 'beforechange' + ); + this.on('afterlayout', this.onFirstLayout, this, {single: true}); + this.cursor = 0; + this.bindStore(this.store); + }, + + // private + onFirstLayout : function(){ + if(this.dsLoaded){ + this.onLoad.apply(this, this.dsLoaded); + } + }, + + // private + updateInfo : function(){ + if(this.displayItem){ + var count = this.store.getCount(); + var msg = count == 0 ? + this.emptyMsg : + String.format( + this.displayMsg, + this.cursor+1, this.cursor+count, this.store.getTotalCount() + ); + this.displayItem.setText(msg); + } + }, + + // private + onLoad : function(store, r, o){ + if(!this.rendered){ + this.dsLoaded = [store, r, o]; + return; + } + var p = this.getParams(); + this.cursor = (o.params && o.params[p.start]) ? o.params[p.start] : 0; + var d = this.getPageData(), ap = d.activePage, ps = d.pages; + + this.afterTextItem.setText(String.format(this.afterPageText, d.pages)); + this.inputItem.setValue(ap); + this.first.setDisabled(ap == 1); + this.prev.setDisabled(ap == 1); + this.next.setDisabled(ap == ps); + this.last.setDisabled(ap == ps); + this.refresh.enable(); + this.updateInfo(); + this.fireEvent('change', this, d); + }, + + // private + getPageData : function(){ + var total = this.store.getTotalCount(); + return { + total : total, + activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize), + pages : total < this.pageSize ? 1 : Math.ceil(total/this.pageSize) + }; + }, + + /** + * Change the active page + * @param {Integer} page The page to display + */ + changePage : function(page){ + this.doLoad(((page-1) * this.pageSize).constrain(0, this.store.getTotalCount())); + }, + + // private + onLoadError : function(){ + if(!this.rendered){ + return; + } + this.refresh.enable(); + }, + + // private + readPage : function(d){ + var v = this.inputItem.getValue(), pageNum; + if (!v || isNaN(pageNum = parseInt(v, 10))) { + this.inputItem.setValue(d.activePage); + return false; + } + return pageNum; + }, + + onPagingFocus : function(){ + this.inputItem.select(); + }, + + //private + onPagingBlur : function(e){ + this.inputItem.setValue(this.getPageData().activePage); + }, + + // private + onPagingKeyDown : function(field, e){ + var k = e.getKey(), d = this.getPageData(), pageNum; + if (k == e.RETURN) { + e.stopEvent(); + pageNum = this.readPage(d); + if(pageNum !== false){ + pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1; + this.doLoad(pageNum * this.pageSize); + } + }else if (k == e.HOME || k == e.END){ + e.stopEvent(); + pageNum = k == e.HOME ? 1 : d.pages; + field.setValue(pageNum); + }else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN){ + e.stopEvent(); + if((pageNum = this.readPage(d))){ + var increment = e.shiftKey ? 10 : 1; + if(k == e.DOWN || k == e.PAGEDOWN){ + increment *= -1; + } + pageNum += increment; + if(pageNum >= 1 & pageNum <= d.pages){ + field.setValue(pageNum); + } + } + } + }, + + // private + getParams : function(){ + //retain backwards compat, allow params on the toolbar itself, if they exist. + return this.paramNames || this.store.paramNames; + }, + + // private + beforeLoad : function(){ + if(this.rendered && this.refresh){ + this.refresh.disable(); + } + }, + + // private + doLoad : function(start){ + var o = {}, pn = this.getParams(); + o[pn.start] = start; + o[pn.limit] = this.pageSize; + if(this.fireEvent('beforechange', this, o) !== false){ + this.store.load({params:o}); + } + }, + + /** + * Move to the first page, has the same effect as clicking the 'first' button. + */ + moveFirst : function(){ + this.doLoad(0); + }, + + /** + * Move to the previous page, has the same effect as clicking the 'previous' button. + */ + movePrevious : function(){ + this.doLoad(Math.max(0, this.cursor-this.pageSize)); + }, + + /** + * Move to the next page, has the same effect as clicking the 'next' button. + */ + moveNext : function(){ + this.doLoad(this.cursor+this.pageSize); + }, + + /** + * Move to the last page, has the same effect as clicking the 'last' button. + */ + moveLast : function(){ + var total = this.store.getTotalCount(), + extra = total % this.pageSize; + + this.doLoad(extra ? (total - extra) : total - this.pageSize); + }, + + /** + * Refresh the current page, has the same effect as clicking the 'refresh' button. + */ + refresh : function(){ + this.doLoad(this.cursor); + }, + + /** + * 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 doLoad; + if(!initial && this.store){ + this.store.un('beforeload', this.beforeLoad, this); + this.store.un('load', this.onLoad, this); + this.store.un('exception', this.onLoadError, this); + if(store !== this.store && this.store.autoDestroy){ + this.store.destroy(); + } + } + if(store){ + store = Ext.StoreMgr.lookup(store); + store.on({ + scope: this, + beforeload: this.beforeLoad, + load: this.onLoad, + exception: this.onLoadError + }); + doLoad = store.getCount() > 0; + } + this.store = store; + if(doLoad){ + this.onLoad(store, null, {}); + } + }, + + /** + * 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); + Ext.PagingToolbar.superclass.onDestroy.call(this); + } +}); + +})(); +Ext.reg('paging', Ext.PagingToolbar); \ No newline at end of file