/** * @class Ext.TabPanel *A basic tab container. TabPanels can be used exactly like a standard {@link Ext.Panel} * for layout purposes, but also have special support for containing child Components * ({@link Ext.Container#items items}) that are managed using a * {@link Ext.layout.CardLayout CardLayout layout manager}, and displayed as separate tabs.
* * Note: By default, a tab's close tool destroys the child tab Component * and all its descendants. This makes the child tab Component, and all its descendants unusable. To enable * re-use of a tab, configure the TabPanel with{@link #autoDestroy autoDestroy: false}
. * *TabPanel header/footer elements
*TabPanels use their {@link Ext.Panel#header header} or {@link Ext.Panel#footer footer} element * (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons. * This means that a TabPanel will not display any configured title, and will not display any * configured header {@link Ext.Panel#tools tools}.
*To display a header, embed the TabPanel in a {@link Ext.Panel Panel} which uses * {@link Ext.Container#layout layout:'fit'}.
* *Tab Events
*There is no actual tab class — each tab is simply a {@link Ext.BoxComponent Component} * such as a {@link Ext.Panel Panel}. However, when rendered in a TabPanel, each child Component * can fire additional events that only exist for tabs and are not available from other Components. * These events are:
***
- {@link Ext.Panel#activate activate} : Fires when this Component becomes * the active tab.
*- {@link Ext.Panel#deactivate deactivate} : Fires when the Component that * was the active tab becomes deactivated.
*- {@link Ext.Panel#beforeclose beforeclose} : Fires when the user clicks on the close tool of a closeable tab. * May be vetoed by returning
*false
from a handler.- {@link Ext.Panel#close close} : Fires a closeable tab has been closed by the user.
*Creating TabPanels from Code
*TabPanels can be created and rendered completely in code, as in this example:
**var tabs = new Ext.TabPanel({ renderTo: Ext.getBody(), activeTab: 0, items: [{ title: 'Tab 1', html: 'A simple tab' },{ title: 'Tab 2', html: 'Another one' }] });
Creating TabPanels from Existing Markup
*TabPanels can also be rendered from pre-existing markup in a couple of ways.
** * @extends Ext.Panel * @constructor * @param {Object} config The configuration options * @xtype tabpanel */ Ext.TabPanel = Ext.extend(Ext.Panel, { /** * @cfg {Boolean} layoutOnTabChange * Set to true to force a layout of the active tab when the tab is changed. Defaults to false. * See {@link Ext.layout.CardLayout}.* *
- Pre-Structured Markup
*** *A container div with one or more nested tab divs with class 'x-tab' can be rendered entirely * from existing markup (See the {@link #autoTabs} example).
*- Un-Structured Markup
*** *A TabPanel can also be rendered from markup that is not strictly structured by simply specifying by id * which elements should be the container and the tabs. Using this method tab content can be pulled from different * elements within the page by id regardless of page structure. For example:
** Note that the tab divs in this example contain the class 'x-hide-display' so that they can be rendered * deferred without displaying outside the tabs. You could alternately set {@link #deferredRender} = false * to render all content tabs on page load. *var tabs = new Ext.TabPanel({ renderTo: 'my-tabs', activeTab: 0, items:[ {contentEl:'tab1', title:'Tab 1'}, {contentEl:'tab2', title:'Tab 2'} ] }); // Note that the tabs do not have to be nested within the container (although they can be) <div id="my-tabs"></div> <div id="tab1" class="x-hide-display">A simple tab</div> <div id="tab2" class="x-hide-display">Another one</div>
{@link Ext.layout.CardLayout#layoutOnCardChange layoutOnCardChange}
. */ /** * @cfg {String} tabCls This config option is used on child Components of ths TabPanel. A CSS * class name applied to the tab strip item representing the child Component, allowing special * styling to be applied. */ /** * @cfg {Boolean} deferredRender *true by default to defer the rendering of child {@link Ext.Container#items items} * to the browsers DOM until a tab is activated. false will render all contained * {@link Ext.Container#items items} as soon as the {@link Ext.layout.CardLayout layout} * is rendered. If there is a significant amount of content or a lot of heavy controls being * rendered into panels that are not displayed by default, setting this to true might * improve performance.
*The deferredRender property is internally passed to the layout manager for * TabPanels ({@link Ext.layout.CardLayout}) as its {@link Ext.layout.CardLayout#deferredRender} * configuration value.
*Note: leaving deferredRender as true means that the content * within an unactivated tab will not be available. For example, this means that if the TabPanel * is within a {@link Ext.form.FormPanel form}, then until a tab is activated, any Fields within * unactivated tabs will not be rendered, and will therefore not be submitted and will not be * available to either {@link Ext.form.BasicForm#getValues getValues} or * {@link Ext.form.BasicForm#setValues setValues}.
*/ deferredRender : true, /** * @cfg {Number} tabWidth The initial width in pixels of each new tab (defaults to 120). */ tabWidth : 120, /** * @cfg {Number} minTabWidth The minimum width in pixels for each tab when {@link #resizeTabs} = true (defaults to 30). */ minTabWidth : 30, /** * @cfg {Boolean} resizeTabs True to automatically resize each tab so that the tabs will completely fill the * tab strip (defaults to false). Setting this to true may cause specific widths that might be set per tab to * be overridden in order to fit them all into view (although {@link #minTabWidth} will always be honored). */ resizeTabs : false, /** * @cfg {Boolean} enableTabScroll True to enable scrolling to tabs that may be invisible due to overflowing the * overall TabPanel width. Only available with tabPosition:'top' (defaults to false). */ enableTabScroll : false, /** * @cfg {Number} scrollIncrement The number of pixels to scroll each time a tab scroll button is pressed * (defaults to 100, or if {@link #resizeTabs} = true, the calculated tab width). Only * applies when {@link #enableTabScroll} = true. */ scrollIncrement : 0, /** * @cfg {Number} scrollRepeatInterval Number of milliseconds between each scroll while a tab scroll button is * continuously pressed (defaults to 400). */ scrollRepeatInterval : 400, /** * @cfg {Float} scrollDuration The number of milliseconds that each scroll animation should last (defaults * to .35). Only applies when {@link #animScroll} = true. */ scrollDuration : 0.35, /** * @cfg {Boolean} animScroll True to animate tab scrolling so that hidden tabs slide smoothly into view (defaults * to true). Only applies when {@link #enableTabScroll} = true. */ animScroll : true, /** * @cfg {String} tabPosition The position where the tab strip should be rendered (defaults to 'top'). * The only other supported value is 'bottom'. Note: tab scrolling is only supported for * tabPosition: 'top'. */ tabPosition : 'top', /** * @cfg {String} baseCls The base CSS class applied to the panel (defaults to 'x-tab-panel'). */ baseCls : 'x-tab-panel', /** * @cfg {Boolean} autoTabs *true to query the DOM for any divs with a class of 'x-tab' to be automatically converted * to tabs and added to this panel (defaults to false). Note that the query will be executed within * the scope of the container element only (so that multiple tab panels from markup can be supported via this * method).
*This method is only possible when the markup is structured correctly as a container with nested divs * containing the class 'x-tab'. To create TabPanels without these limitations, or to pull tab content * from other elements on the page, see the example at the top of the class for generating tabs from markup.
*There are a couple of things to note when using this method:
var tabs = new Ext.TabPanel({
applyTo: 'my-tabs',
activeTab: 0,
deferredRender: false,
autoTabs: true
});
// This markup will be converted to a TabPanel from the code above
<div id="my-tabs">
<div class="x-tab" title="Tab 1">A simple tab</div>
<div class="x-tab" title="Tab 2">Another one</div>
</div>
*/
autoTabs : false,
/**
* @cfg {String} autoTabSelector The CSS selector used to search for tabs in existing markup when
* {@link #autoTabs} = true (defaults to 'div.x-tab'). This can be any valid selector
* supported by {@link Ext.DomQuery#select}. Note that the query will be executed within the scope of this
* tab panel only (so that multiple tab panels from markup can be supported on a page).
*/
autoTabSelector : 'div.x-tab',
/**
* @cfg {String/Number} activeTab A string id or the numeric index of the tab that should be initially
* activated on render (defaults to undefined).
*/
activeTab : undefined,
/**
* @cfg {Number} tabMargin The number of pixels of space to calculate into the sizing and scrolling of
* tabs. If you change the margin in CSS, you will need to update this value so calculations are correct
* with either {@link #resizeTabs} or scrolling tabs. (defaults to 2)
*/
tabMargin : 2,
/**
* @cfg {Boolean} plain true to render the tab strip without a background container image
* (defaults to false).
*/
plain : false,
/**
* @cfg {Number} wheelIncrement For scrolling tabs, the number of pixels to increment on mouse wheel
* scrolling (defaults to 20).
*/
wheelIncrement : 20,
/*
* This is a protected property used when concatenating tab ids to the TabPanel id for internal uniqueness.
* It does not generally need to be changed, but can be if external code also uses an id scheme that can
* potentially clash with this one.
*/
idDelimiter : '__',
// private
itemCls : 'x-tab-item',
// private config overrides
elements : 'body',
headerAsText : false,
frame : false,
hideBorders :true,
// private
initComponent : function(){
this.frame = false;
Ext.TabPanel.superclass.initComponent.call(this);
this.addEvents(
/**
* @event beforetabchange
* Fires before the active tab changes. Handlers can return false to cancel the tab change.
* @param {TabPanel} this
* @param {Panel} newTab The tab being activated
* @param {Panel} currentTab The current active tab
*/
'beforetabchange',
/**
* @event tabchange
* Fires after the active tab has changed.
* @param {TabPanel} this
* @param {Panel} tab The new active tab
*/
'tabchange',
/**
* @event contextmenu
* Relays the contextmenu event from a tab selector element in the tab strip.
* @param {TabPanel} this
* @param {Panel} tab The target tab
* @param {EventObject} e
*/
'contextmenu'
);
/**
* @cfg {Object} layoutConfig
* TabPanel implicitly uses {@link Ext.layout.CardLayout} as its layout manager.
* layoutConfig
may be used to configure this layout manager.
* {@link #deferredRender}
and {@link #layoutOnTabChange}
* configured on the TabPanel will be applied as configs to the layout manager.
*/
this.setLayout(new Ext.layout.CardLayout(Ext.apply({
layoutOnCardChange: this.layoutOnTabChange,
deferredRender: this.deferredRender
}, this.layoutConfig)));
if(this.tabPosition == 'top'){
this.elements += ',header';
this.stripTarget = 'header';
}else {
this.elements += ',footer';
this.stripTarget = 'footer';
}
if(!this.stack){
this.stack = Ext.TabPanel.AccessStack();
}
this.initItems();
},
// private
onRender : function(ct, position){
Ext.TabPanel.superclass.onRender.call(this, ct, position);
if(this.plain){
var pos = this.tabPosition == 'top' ? 'header' : 'footer';
this[pos].addClass('x-tab-panel-'+pos+'-plain');
}
var st = this[this.stripTarget];
this.stripWrap = st.createChild({cls:'x-tab-strip-wrap', cn:{
tag:'ul', cls:'x-tab-strip x-tab-strip-'+this.tabPosition}});
var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
st.createChild({cls:'x-tab-strip-spacer'}, beforeEl);
this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
// create an empty span with class x-tab-strip-text to force the height of the header element when there's no tabs.
this.edge = this.strip.createChild({tag:'li', cls:'x-tab-edge', cn: [{tag: 'span', cls: 'x-tab-strip-text', cn: ' '}]});
this.strip.createChild({cls:'x-clear'});
this.body.addClass('x-tab-panel-body-'+this.tabPosition);
/**
* @cfg {Template/XTemplate} itemTpl (Optional) A {@link Ext.Template Template} or * {@link Ext.XTemplate XTemplate} which may be provided to process the data object returned from * {@link #getTemplateArgs} to produce a clickable selector element in the tab strip.
*The main element created should be a <li> element. In order for a click event on * a selector element to be connected to its item, it must take its id from the TabPanel's * native {@link #getTemplateArgs}.
*The child element which contains the title text must be marked by the CSS class * x-tab-strip-inner.
*To enable closability, the created element should contain an element marked by the CSS class * x-tab-strip-close.
*If a custom itemTpl is supplied, it is the developer's responsibility to create CSS * style rules to create the desired appearance.
* Below is an example of how to create customized tab selector items:
new Ext.TabPanel({
renderTo: document.body,
minTabWidth: 115,
tabWidth: 135,
enableTabScroll: true,
width: 600,
height: 250,
defaults: {autoScroll:true},
itemTpl: new Ext.XTemplate(
'<li class="{cls}" id="{id}" style="overflow:hidden">',
'<tpl if="closable">',
'<a class="x-tab-strip-close"></a>',
'</tpl>',
'<a class="x-tab-right" href="#" style="padding-left:6px">',
'<em class="x-tab-left">',
'<span class="x-tab-strip-inner">',
'<img src="{src}" style="float:left;margin:3px 3px 0 0">',
'<span style="margin-left:20px" class="x-tab-strip-text {iconCls}">{text} {extra}</span>',
'</span>',
'</em>',
'</a>',
'</li>'
),
getTemplateArgs: function(item) {
// Call the native method to collect the base data. Like the ID!
var result = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);
// Add stuff used in our template
return Ext.apply(result, {
closable: item.closable,
src: item.iconSrc,
extra: item.extraText || ''
});
},
items: [{
title: 'New Tab 1',
iconSrc: '../shared/icons/fam/grid.png',
html: 'Tab Body 1',
closable: true
}, {
title: 'New Tab 2',
iconSrc: '../shared/icons/fam/grid.png',
html: 'Tab Body 2',
extraText: 'Extra stuff in the tab button'
}]
});
*/
if(!this.itemTpl){
var tt = new Ext.Template(
'Provides template arguments for rendering a tab selector item in the tab strip.
*This method returns an object hash containing properties used by the TabPanel's {@link #itemTpl} * to create a formatted, clickable tab selector element. The properties which must be returned * are:
{@link Ext.Component#itemId itemId}
* or {@link Ext.Component#id id}
of the child component {@link Ext.Container#items items}
propertyFor additional information see {@link Ext.util.MixedCollection#get}. */ setActiveTab : function(item){ item = this.getComponent(item); if(this.fireEvent('beforetabchange', this, item, this.activeTab) === false){ return; } if(!this.rendered){ this.activeTab = item; return; } if(this.activeTab != item){ if(this.activeTab){ var oldEl = this.getTabEl(this.activeTab); if(oldEl){ Ext.fly(oldEl).removeClass('x-tab-strip-active'); } } if(item){ var el = this.getTabEl(item); Ext.fly(el).addClass('x-tab-strip-active'); this.activeTab = item; this.stack.add(item); this.layout.setActiveItem(item); if(this.scrolling){ this.scrollToTab(item, this.animScroll); } } this.fireEvent('tabchange', this, item); } },
/** * Returns the Component which is the currently active tab. Note that before the TabPanel * first activates a child Component, this method will return whatever was configured in the * {@link #activeTab} config option. * @return {BoxComponent} The currently active child Component if one is active, or the {@link #activeTab} config value. */ getActiveTab : function(){ return this.activeTab || null; }, /** * Gets the specified tab by id. * @param {String} id The tab id * @return {Panel} The tab */ getItem : function(item){ return this.getComponent(item); }, // private autoScrollTabs : function(){ this.pos = this.tabPosition=='bottom' ? this.footer : this.header; var count = this.items.length, ow = this.pos.dom.offsetWidth, tw = this.pos.dom.clientWidth, wrap = this.stripWrap, wd = wrap.dom, cw = wd.offsetWidth, pos = this.getScrollPos(), l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos; if(!this.enableTabScroll || count < 1 || cw < 20){ // 20 to prevent display:none issues return; } if(l <= tw){ wd.scrollLeft = 0; wrap.setWidth(tw); if(this.scrolling){ this.scrolling = false; this.pos.removeClass('x-tab-scrolling'); this.scrollLeft.hide(); this.scrollRight.hide(); // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari if(Ext.isAir || Ext.isWebKit){ wd.style.marginLeft = ''; wd.style.marginRight = ''; } } }else{ if(!this.scrolling){ this.pos.addClass('x-tab-scrolling'); // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari if(Ext.isAir || Ext.isWebKit){ wd.style.marginLeft = '18px'; wd.style.marginRight = '18px'; } } tw -= wrap.getMargins('lr'); wrap.setWidth(tw > 20 ? tw : 20); if(!this.scrolling){ if(!this.scrollLeft){ this.createScrollers(); }else{ this.scrollLeft.show(); this.scrollRight.show(); } } this.scrolling = true; if(pos > (l-tw)){ // ensure it stays within bounds wd.scrollLeft = l-tw; }else{ // otherwise, make sure the active tab is still visible this.scrollToTab(this.activeTab, false); } this.updateScrollButtons(); } }, // private createScrollers : function(){ this.pos.addClass('x-tab-scrolling-' + this.tabPosition); var h = this.stripWrap.dom.offsetHeight; // left var sl = this.pos.insertFirst({ cls:'x-tab-scroller-left' }); sl.setHeight(h); sl.addClassOnOver('x-tab-scroller-left-over'); this.leftRepeater = new Ext.util.ClickRepeater(sl, { interval : this.scrollRepeatInterval, handler: this.onScrollLeft, scope: this }); this.scrollLeft = sl; // right var sr = this.pos.insertFirst({ cls:'x-tab-scroller-right' }); sr.setHeight(h); sr.addClassOnOver('x-tab-scroller-right-over'); this.rightRepeater = new Ext.util.ClickRepeater(sr, { interval : this.scrollRepeatInterval, handler: this.onScrollRight, scope: this }); this.scrollRight = sr; }, // private getScrollWidth : function(){ return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos(); }, // private getScrollPos : function(){ return parseInt(this.stripWrap.dom.scrollLeft, 10) || 0; }, // private getScrollArea : function(){ return parseInt(this.stripWrap.dom.clientWidth, 10) || 0; }, // private getScrollAnim : function(){ return {duration:this.scrollDuration, callback: this.updateScrollButtons, scope: this}; }, // private getScrollIncrement : function(){ return this.scrollIncrement || (this.resizeTabs ? this.lastTabWidth+2 : 100); }, /** * Scrolls to a particular tab if tab scrolling is enabled * @param {Panel} item The item to scroll to * @param {Boolean} animate True to enable animations */ scrollToTab : function(item, animate){ if(!item){ return; } var el = this.getTabEl(item), pos = this.getScrollPos(), area = this.getScrollArea(), left = Ext.fly(el).getOffsetsTo(this.stripWrap)[0] + pos, right = left + el.offsetWidth; if(left < pos){ this.scrollTo(left, animate); }else if(right > (pos + area)){ this.scrollTo(right - area, animate); } }, // private scrollTo : function(pos, animate){ this.stripWrap.scrollTo('left', pos, animate ? this.getScrollAnim() : false); if(!animate){ this.updateScrollButtons(); } }, onWheel : function(e){ var d = e.getWheelDelta()*this.wheelIncrement*-1; e.stopEvent(); var pos = this.getScrollPos(), newpos = pos + d, sw = this.getScrollWidth()-this.getScrollArea(); var s = Math.max(0, Math.min(sw, newpos)); if(s != pos){ this.scrollTo(s, false); } }, // private onScrollRight : function(){ var sw = this.getScrollWidth()-this.getScrollArea(), pos = this.getScrollPos(), s = Math.min(sw, pos + this.getScrollIncrement()); if(s != pos){ this.scrollTo(s, this.animScroll); } }, // private onScrollLeft : function(){ var pos = this.getScrollPos(), s = Math.max(0, pos - this.getScrollIncrement()); if(s != pos){ this.scrollTo(s, this.animScroll); } }, // private updateScrollButtons : function(){ var pos = this.getScrollPos(); this.scrollLeft[pos === 0 ? 'addClass' : 'removeClass']('x-tab-scroller-left-disabled'); this.scrollRight[pos >= (this.getScrollWidth()-this.getScrollArea()) ? 'addClass' : 'removeClass']('x-tab-scroller-right-disabled'); }, // private beforeDestroy : function() { Ext.destroy(this.leftRepeater, this.rightRepeater); this.deleteMembers('strip', 'edge', 'scrollLeft', 'scrollRight', 'stripWrap'); this.activeTab = null; Ext.TabPanel.superclass.beforeDestroy.apply(this); } /** * @cfg {Boolean} collapsible * @hide */ /** * @cfg {String} header * @hide */ /** * @cfg {Boolean} headerAsText * @hide */ /** * @property header * @hide */ /** * @cfg title * @hide */ /** * @cfg {Array} tools * @hide */ /** * @cfg {Array} toolTemplate * @hide */ /** * @cfg {Boolean} hideCollapseTool * @hide */ /** * @cfg {Boolean} titleCollapse * @hide */ /** * @cfg {Boolean} collapsed * @hide */ /** * @cfg {String} layout * @hide */ /** * @cfg {Boolean} preventBodyReset * @hide */ }); Ext.reg('tabpanel', Ext.TabPanel); /** * See {@link #setActiveTab}. Sets the specified tab as the active tab. This method fires * the {@link #beforetabchange} event which can return false to cancel the tab change. * @param {String/Panel} tab The id or tab Panel to activate * @method activate */ Ext.TabPanel.prototype.activate = Ext.TabPanel.prototype.setActiveTab; // private utility class used by TabPanel Ext.TabPanel.AccessStack = function(){ var items = []; return { add : function(item){ items.push(item); if(items.length > 10){ items.shift(); } }, remove : function(item){ var s = []; for(var i = 0, len = items.length; i < len; i++) { if(items[i] != item){ s.push(items[i]); } } items = s; }, next : function(){ return items.pop(); } }; };