Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

844 lines
27 KiB

/**
* A basic tab container. TabPanels can be used exactly like a standard {@link Ext.panel.Panel} for
* layout purposes, but also have special support for containing child Components
* (`{@link Ext.container.Container#cfg-items items}`) that are managed using a
* {@link Ext.layout.container.Card 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's layout
*
* TabPanels use a Dock layout to position the {@link Ext.tab.Bar TabBar} at the top of the widget.
* Panels added to the TabPanel will have their header hidden by default because the Tab will
* automatically take the Panel's configured title and icon.
*
* TabPanels use their {@link Ext.panel.Header header} or {@link Ext.panel.Panel#fbar 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.Panel#tools tools}.
*
* To display a header, embed the TabPanel in a {@link Ext.panel.Panel Panel} which uses
* `{@link Ext.container.Container#layout layout: 'fit'}`.
*
* ## Controlling tabs
*
* Configuration options for the {@link Ext.tab.Tab} that represents the component can be passed in
* by specifying the tabConfig option:
*
* @example
* Ext.tip.QuickTipManager.init();
* Ext.create('Ext.tab.Panel', {
* width: 400,
* height: 400,
* renderTo: document.body,
* items: [{
* title: 'Foo'
* }, {
* title: 'Bar',
* tabConfig: {
* title: 'Custom Title',
* tooltip: 'A button tooltip'
* }
* }]
* });
*
* ## Vetoing Changes
*
* User interaction when changing the tabs can be vetoed by listening to the {@link #beforetabchange} event.
* By returning `false`, the tab change will not occur.
*
* @example
* Ext.create('Ext.tab.Panel', {
* renderTo: Ext.getBody(),
* width: 200,
* height: 200,
* listeners: {
* beforetabchange: function(tabs, newTab, oldTab) {
* return newTab.title != 'P2';
* }
* },
* items: [{
* title: 'P1'
* }, {
* title: 'P2'
* }, {
* title: 'P3'
* }]
* });
*
* # Examples
*
* Here is a basic TabPanel rendered to the body. This also shows the useful configuration {@link #activeTab},
* which allows you to set the active tab on render.
*
* @example
* Ext.create('Ext.tab.Panel', {
* width: 300,
* height: 200,
* activeTab: 0,
* items: [
* {
* title: 'Tab 1',
* bodyPadding: 10,
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* It is easy to control the visibility of items in the tab bar. Specify hidden: true to have the
* tab button hidden initially. Items can be subsequently hidden and show by accessing the
* tab property on the child item.
*
* @example
* var tabs = Ext.create('Ext.tab.Panel', {
* width: 400,
* height: 400,
* renderTo: document.body,
* items: [{
* title: 'Home',
* html: 'Home',
* itemId: 'home'
* }, {
* title: 'Users',
* html: 'Users',
* itemId: 'users',
* hidden: true
* }, {
* title: 'Tickets',
* html: 'Tickets',
* itemId: 'tickets'
* }]
* });
*
* Ext.defer(function(){
* tabs.child('#home').tab.hide();
* var users = tabs.child('#users');
* users.tab.show();
* tabs.setActiveTab(users);
* }, 1000);
*
* You can remove the background of the TabBar by setting the {@link #plain} property to `true`.
*
* @example
* Ext.create('Ext.tab.Panel', {
* width: 300,
* height: 200,
* activeTab: 0,
* plain: true,
* items: [
* {
* title: 'Tab 1',
* bodyPadding: 10,
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* Another useful configuration of TabPanel is {@link #tabPosition}. This allows you to change the
* position where the tabs are displayed. The available options for this are `'top'` (default) and
* `'bottom'`.
*
* @example
* Ext.create('Ext.tab.Panel', {
* width: 300,
* height: 200,
* activeTab: 0,
* bodyPadding: 10,
* tabPosition: 'bottom',
* items: [
* {
* title: 'Tab 1',
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* The {@link #setActiveTab} is a very useful method in TabPanel which will allow you to change the
* current active tab. You can either give it an index or an instance of a tab. For example:
*
* @example
* var tabs = Ext.create('Ext.tab.Panel', {
* items: [
* {
* id : 'my-tab',
* title: 'Tab 1',
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* var tab = Ext.getCmp('my-tab');
*
* Ext.create('Ext.button.Button', {
* renderTo: Ext.getBody(),
* text : 'Select the first tab',
* scope : this,
* handler : function() {
* tabs.setActiveTab(tab);
* }
* });
*
* Ext.create('Ext.button.Button', {
* text : 'Select the second tab',
* scope : this,
* handler : function() {
* tabs.setActiveTab(1);
* },
* renderTo : Ext.getBody()
* });
*
* The {@link #getActiveTab} is a another useful method in TabPanel which will return the current active tab.
*
* @example
* var tabs = Ext.create('Ext.tab.Panel', {
* items: [
* {
* title: 'Tab 1',
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* Ext.create('Ext.button.Button', {
* text : 'Get active tab',
* scope : this,
* handler : function() {
* var tab = tabs.getActiveTab();
* alert('Current tab: ' + tab.title);
* },
* renderTo : Ext.getBody()
* });
*
* Adding a new tab is very simple with a TabPanel. You simple call the {@link #method-add} method with an config
* object for a panel.
*
* @example
* var tabs = Ext.create('Ext.tab.Panel', {
* items: [
* {
* title: 'Tab 1',
* html : 'A simple tab'
* },
* {
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* Ext.create('Ext.button.Button', {
* text : 'New tab',
* scope : this,
* handler : function() {
* var tab = tabs.add({
* // we use the tabs.items property to get the length of current items/tabs
* title: 'Tab ' + (tabs.items.length + 1),
* html : 'Another one'
* });
*
* tabs.setActiveTab(tab);
* },
* renderTo : Ext.getBody()
* });
*
* Additionally, removing a tab is very also simple with a TabPanel. You simple call the {@link #method-remove} method
* with an config object for a panel.
*
* @example
* var tabs = Ext.create('Ext.tab.Panel', {
* items: [
* {
* title: 'Tab 1',
* html : 'A simple tab'
* },
* {
* id : 'remove-this-tab',
* title: 'Tab 2',
* html : 'Another one'
* }
* ],
* renderTo : Ext.getBody()
* });
*
* Ext.create('Ext.button.Button', {
* text : 'Remove tab',
* scope : this,
* handler : function() {
* var tab = Ext.getCmp('remove-this-tab');
* tabs.remove(tab);
* },
* renderTo : Ext.getBody()
* });
*/
Ext.define('Ext.tab.Panel', {
extend: 'Ext.panel.Panel',
alias: 'widget.tabpanel',
alternateClassName: ['Ext.TabPanel'],
requires: ['Ext.layout.container.Card', 'Ext.tab.Bar'],
config: {
// @cmd-auto-dependency { directRef: 'Ext.tab.Bar' }
/**
* @cfg {Object} tabBar
* Optional configuration object for the internal {@link Ext.tab.Bar}.
* If present, this is passed straight through to the TabBar's constructor
*/
tabBar: undefined,
/**
* @cfg {"top"/"bottom"/"left"/"right"} tabPosition
* The position where the tab strip should be rendered. Possible values are:
*
* - top
* - bottom
* - left
* - right
*/
tabPosition : 'top',
/**
* @cfg {'default'/0/1/2} tabRotation
* The rotation of the tabs. Can be one of the following values:
*
* - `'default'` use the default rotation, depending on {@link #tabPosition} (see below)
* - `0` - no rotation
* - `1` - rotate 90deg clockwise
* - `2` - rotate 90deg counter-clockwise
*
* The default behavior of this config depends on the {@link #tabPosition}:
*
* - `'top'` or `'bottom'` - `0`
* - `'right'` - `1`
* - `'left'` - `2`
*/
tabRotation: 'default',
/**
* @cfg {Boolean} tabStretchMax
* `true` to stretch all tabs to the height of the tallest tab when the tabBar
* is docked horizontally, or the width of the widest tab when the tabBar is
* docked vertically.
*/
tabStretchMax: true
},
/**
* @cfg {Number} tabBarHeaderPosition
* If specified, the {@link #tabBar} will be rendered as an item of the TabPanel's
* Header and the specified `tabBarHeaderPosition` will be used as the Panel header's
* {@link Ext.panel.Header#itemPosition}. If not specified, the {@link #tabBar}
* will be rendered as a docked item at {@link #tabPosition}.
*/
/**
* @cfg {String/Number} activeItem
* Doesn't apply for {@link Ext.tab.Panel TabPanel}, use {@link #activeTab} instead.
*/
/**
* @cfg {String/Number/Ext.Component} activeTab
* The tab to activate initially. Either an ID, index or the tab component itself. If null, no tab will be set as active.
*/
/**
* @cfg {Ext.enums.Layout/Object} layout
* Optional configuration object for the internal {@link Ext.layout.container.Card card layout}.
* If present, this is passed straight through to the layout's constructor
*/
/**
* @cfg {Boolean} removePanelHeader
* True to instruct each Panel added to the TabContainer to not render its header element.
* This is to ensure that the title of the panel does not appear twice.
*/
removePanelHeader: true,
/**
* @cfg {Boolean} plain
* True to not show the full background on the TabBar.
*/
plain: false,
/**
* @cfg {String} [itemCls='x-tabpanel-child']
* The class added to each child item of this TabPanel.
*/
itemCls: Ext.baseCSSPrefix + 'tabpanel-child',
/**
* @cfg {Number} minTabWidth
* The minimum width for a tab in the {@link #cfg-tabBar}.
*/
minTabWidth: undefined,
/**
* @cfg {Number} maxTabWidth The maximum width for each tab.
*/
maxTabWidth: undefined,
/**
* @cfg {Boolean} deferredRender
*
* True by default to defer the rendering of child {@link Ext.container.Container#cfg-items items} to the browsers DOM
* until a tab is activated. False will render all contained {@link Ext.container.Container#cfg-items items} as soon as
* the {@link Ext.layout.container.Card 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.container.Card}) as its {@link Ext.layout.container.Card#deferredRender} configuration value.
*
* **Note**: leaving deferredRender as true means that the content within an unactivated tab will not be available
*/
deferredRender : true,
_defaultTabRotation: {
top: 0,
right: 1,
bottom: 0,
left: 2
},
/**
* @event beforetabchange
* Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
* the tabchange
* @param {Ext.tab.Panel} tabPanel The TabPanel
* @param {Ext.Component} newCard The card that is about to be activated
* @param {Ext.Component} oldCard The card that is currently active
*/
/**
* @event tabchange
* Fires when a new tab has been activated (activated by {@link #setActiveTab}).
* @param {Ext.tab.Panel} tabPanel The TabPanel
* @param {Ext.Component} newCard The newly activated item
* @param {Ext.Component} oldCard The previously active item
*/
//inherit docs
initComponent: function() {
var me = this,
// Default to 0 if undefined and not null!
activeTab = me.activeTab !== null ? (me.activeTab || 0) : null,
dockedItems = me.dockedItems,
header = me.header,
tabBarHeaderPosition = me.tabBarHeaderPosition,
tabBar = me.getTabBar(),
headerItems;
// Configure the layout with our deferredRender, and with our activeTeb
me.layout = new Ext.layout.container.Card(Ext.apply({
owner: me,
deferredRender: me.deferredRender,
itemCls: me.itemCls,
activeItem: activeTab
}, me.layout));
if (tabBarHeaderPosition != null) {
header = me.header = Ext.apply({}, header);
headerItems = header.items = (header.items ? header.items.slice() : []);
header.itemPosition = tabBarHeaderPosition;
headerItems.push(tabBar);
header.hasTabBar = true;
} else {
dockedItems = [].concat(me.dockedItems || []);
dockedItems.push(tabBar);
me.dockedItems = dockedItems;
}
me.callParent(arguments);
// We have to convert the numeric index/string ID config into its component reference
activeTab = me.activeTab = me.getComponent(activeTab);
// Ensure that the active child's tab is rendered in the active UI state
if (activeTab) {
tabBar.setActiveTab(activeTab.tab, true);
}
},
/**
* @method getTabBar
* Returns the {@link Ext.tab.Bar} associated with this tabPanel.
* @return {Ext.tab.Bar} The tabBar for this tabPanel
*/
/**
* @method setTabBar
* @hide
*/
onRender: function() {
var items = this.items.items,
len = items.length,
i;
this.callParent(arguments);
// We want to force any view model for the panel to be created.
// This is because we capture various parts about the panel itself (title, icon, etc)
// So we need to be able to use that to populate the tabs.
// In the future, this could be optimized to be a bit smarter to prevent creation when
// not required.
for (i = 0; i < len; ++i) {
items[i].getBind();
}
},
/**
* Makes the given card active. Makes it the visible card in the TabPanel's CardLayout and highlights the Tab.
* @param {String/Number/Ext.Component} card The card to make active. Either an ID, index or the component itself.
* @return {Ext.Component} The resulting active child Component. The call may have been vetoed, or otherwise
* modified by an event listener.
*/
setActiveTab: function(card) {
var me = this,
previous;
// Check for a config object
if (!Ext.isObject(card) || card.isComponent) {
card = me.getComponent(card);
}
previous = me.getActiveTab();
if (card) {
Ext.suspendLayouts();
// We may be passed a config object, so add it.
// Without doing a layout!
if (!card.isComponent) {
card = me.add(card);
}
if (previous === card || me.fireEvent('beforetabchange', me, card, previous) === false) {
Ext.resumeLayouts(true);
return previous;
}
// MUST set the activeTab first so that the machinery which listens for show doesn't
// think that the show is "driving" the activation and attempt to recurse into here.
me.activeTab = card;
// Attempt to switch to the requested card. Suspend layouts because if that was successful
// we have to also update the active tab in the tab bar which is another layout operation
// and we must coalesce them.
me.layout.setActiveItem(card);
// Read the result of the card layout. Events dear boy, events!
card = me.activeTab = me.layout.getActiveItem();
// Card switch was not vetoed by an event listener
if (card && card !== previous) {
// Update the active tab in the tab bar and resume layouts.
me.tabBar.setActiveTab(card.tab);
Ext.resumeLayouts(true);
// previous will be undefined or this.activeTab at instantiation
if (previous !== card) {
me.fireEvent('tabchange', me, card, previous);
}
}
// Card switch was vetoed by an event listener. Resume layouts (Nothing should have changed on a veto).
else {
Ext.resumeLayouts(true);
}
return card;
}
return previous;
},
setActiveItem: function(item) {
return this.setActiveTab(item);
},
/**
* Returns the item that is currently active inside this TabPanel.
* @return {Ext.Component} The currently active item.
*/
getActiveTab: function() {
var me = this,
// Ensure the calculated result references a Component
result = me.getComponent(me.activeTab);
// Sanitize the result in case the active tab is no longer there.
if (result && me.items.indexOf(result) !== -1) {
me.activeTab = result;
} else {
me.activeTab = undefined;
}
return me.activeTab;
},
applyTabBar: function(tabBar) {
var me = this,
// if we are rendering the tabbar into the panel header, use same alignment
// as header position, and ignore tabPosition.
dock = (me.tabBarHeaderPosition != null) ? me.getHeaderPosition() : me.getTabPosition();
return new Ext.tab.Bar(Ext.apply({
ui: me.ui,
dock: dock,
tabRotation: me.getTabRotation(),
vertical: (dock === 'left' || dock === 'right'),
plain: me.plain,
tabStretchMax: me.getTabStretchMax(),
tabPanel: me
}, tabBar));
},
updateHeaderPosition: function(headerPosition, oldHeaderPosition) {
var tabBar = this.getTabBar();
if (tabBar && (this.tabBarHeaderPosition != null)) {
tabBar.setDock(headerPosition);
}
this.callParent([headerPosition, oldHeaderPosition]);
},
updateTabPosition: function(tabPosition) {
var tabBar = this.getTabBar();
if (tabBar && (this.tabBarHeaderPosition == null)) {
tabBar.setDock(tabPosition);
}
},
updateTabRotation: function(tabRotation) {
var tabBar = this.getTabBar();
if (tabBar) {
tabBar.setTabRotation(tabRotation);
}
},
/**
* @protected
* Makes sure we have a Tab for each item added to the TabPanel
*/
onAdd: function(item, index) {
var me = this,
cfg = Ext.apply({}, item.tabConfig),
tabBar = me.getTabBar(),
defaultConfig = {
xtype: 'tab',
title: item.title,
icon: item.icon,
iconCls: item.iconCls,
glyph: item.glyph,
ui: tabBar.ui,
card: item,
disabled: item.disabled,
closable: item.closable,
hidden: item.hidden && !item.hiddenByLayout, // only hide if it wasn't hidden by the layout itself
tooltip: item.tooltip,
tabBar: tabBar,
tabPosition: tabBar.dock,
rotation: tabBar.getTabRotation()
};
if (item.closeText !== undefined) {
defaultConfig.closeText = item.closeText;
}
cfg = Ext.applyIf(cfg, defaultConfig);
// Create the correspondiong tab in the tab bar
item.tab = me.tabBar.insert(index, cfg);
item.on({
scope : me,
enable: me.onItemEnable,
disable: me.onItemDisable,
beforeshow: me.onItemBeforeShow,
iconchange: me.onItemIconChange,
iconclschange: me.onItemIconClsChange,
glyphchange: me.onItemGlyphChange,
titlechange: me.onItemTitleChange
});
if (item.isPanel) {
if (me.removePanelHeader) {
if (item.rendered) {
if (item.header) {
item.header.hide();
}
} else {
item.header = false;
}
}
if (item.isPanel && me.border) {
item.setBorder(false);
}
}
// Force the view model to be created, see onRender
if (me.rendered) {
item.getBind();
}
// Ensure that there is at least one active tab. This is only needed when adding tabs via a loader config, i.e., there
// may be no pre-existing tabs. Note that we need to check if activeTab was explicitly set to `null` in the tabpanel
// config (which tells the layout not to set an active item), as this is a valid value to mean 'do not set an active tab'.
if (me.rendered && me.loader && me.activeTab === undefined && me.layout.activeItem !== null) {
me.setActiveTab(0);
}
},
onMove: function(item, fromIdx, toIdx) {
var tabBar = this.getTabBar();
this.callParent([item, fromIdx, toIdx]);
// If the move of the item.tab triggered the movement of the child Panel,
// then we're done.
if (tabBar.items.indexOf(item.tab) !== toIdx) {
tabBar.move(item.tab, toIdx);
}
},
/**
* @private
* Enable corresponding tab when item is enabled.
*/
onItemEnable: function(item){
item.tab.enable();
},
/**
* @private
* Disable corresponding tab when item is enabled.
*/
onItemDisable: function(item){
item.tab.disable();
},
/**
* @private
* Sets activeTab before item is shown.
*/
onItemBeforeShow: function(item) {
if (item !== this.activeTab) {
this.setActiveTab(item);
return false;
}
},
/**
* @private
* Update the tab icon when panel glyph has been set or changed.
*/
onItemGlyphChange: function(item, newGlyph) {
item.tab.setGlyph(newGlyph);
},
/**
* @private
* Update the tab icon when panel icon has been set or changed.
*/
onItemIconChange: function(item, newIcon) {
item.tab.setIcon(newIcon);
},
/**
* @private
* Update the tab iconCls when panel iconCls has been set or changed.
*/
onItemIconClsChange: function(item, newIconCls) {
item.tab.setIconCls(newIconCls);
},
/**
* @private
* Update the tab title when panel title has been set or changed.
*/
onItemTitleChange: function(item, newTitle) {
item.tab.setText(newTitle);
},
/**
* @private
* Makes sure we remove the corresponding Tab when an item is removed
*/
onRemove: function(item, destroying) {
var me = this;
item.un({
scope : me,
enable: me.onItemEnable,
disable: me.onItemDisable,
beforeshow: me.onItemBeforeShow,
iconchange: me.onItemIconChange,
iconclschange: me.onItemIconClsChange,
glyphchange: me.onItemGlyphChange,
titlechange: me.onItemTitleChange
});
if (item.tab && !me.destroying && item.tab.ownerCt === me.tabBar) {
me.tabBar.remove(item.tab);
}
},
privates: {
/**
* @private
* Unlink the removed child item from its (@link Ext.tab.Tab Tab}.
*
* If we're removing the currently active tab, activate the nearest one. The item is removed when we call super,
* so we can do preprocessing before then to find the card's index
*/
doRemove: function (item, autoDestroy) {
var me = this,
toActivate;
// Destroying, or removing the last item, nothing to activate
if (me.removingAll || me.destroying || me.items.getCount() === 1) {
me.activeTab = null;
}
// Ask the TabBar which tab to activate next.
// Set the active child panel using the index of that tab
else if (item.tab && (toActivate = me.tabBar.items.indexOf(me.tabBar.findNextActivatable(item.tab))) !== -1) {
me.setActiveTab(toActivate);
}
this.callParent(arguments);
if (item.tab) {
// Remove the two references
delete item.tab.card;
delete item.tab;
}
}
}
});