/* This file is part of Ext JS 3.4 Copyright (c) 2011-2013 Sencha Inc Contact: http://www.sencha.com/contact GNU General Public License Usage This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. Build date: 2013-04-03 15:07:25 */ /** * @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:
*false
from a handler.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.
*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).
*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:
*
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>
* 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.
* {@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'); } } this.activeTab = item; if(item){ var el = this.getTabEl(item); Ext.fly(el).addClass('x-tab-strip-active'); this.stack.add(item); this.layout.setActiveItem(item); // Need to do this here, since setting the active tab slightly changes the size this.delegateUpdates(); 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 || cw < 20){ // 20 to prevent display:none issues return; } if(count == 0 || l <= tw){ // ensure the width is set if there's no tabs 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(); } }; };