Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / tab / Tab.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Ed Spencer
17  * @class Ext.tab.Tab
18  * @extends Ext.button.Button
19  *
20  * <p>Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly customized {@link Ext.button.Button Button},
21  * styled to look like a tab. Tabs are optionally closable, and can also be disabled. Typically you will not
22  * need to create Tabs manually as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}</p>
23  */
24 Ext.define('Ext.tab.Tab', {
25     extend: 'Ext.button.Button',
26     alias: 'widget.tab',
27
28     requires: [
29         'Ext.layout.component.Tab',
30         'Ext.util.KeyNav'
31     ],
32
33     componentLayout: 'tab',
34
35     isTab: true,
36
37     baseCls: Ext.baseCSSPrefix + 'tab',
38
39     /**
40      * @cfg {String} activeCls
41      * The CSS class to be applied to a Tab when it is active.
42      * Providing your own CSS for this class enables you to customize the active state.
43      */
44     activeCls: 'active',
45
46     /**
47      * @cfg {String} disabledCls
48      * The CSS class to be applied to a Tab when it is disabled.
49      */
50
51     /**
52      * @cfg {String} closableCls
53      * The CSS class which is added to the tab when it is closable
54      */
55     closableCls: 'closable',
56
57     /**
58      * @cfg {Boolean} closable True to make the Tab start closable (the close icon will be visible).
59      */
60     closable: true,
61
62     /**
63      * @cfg {String} closeText
64      * The accessible text label for the close button link; only used when {@link #closable} = true.
65      */
66     closeText: 'Close Tab',
67
68     /**
69      * @property {Boolean} active
70      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
71      */
72     active: false,
73
74     /**
75      * @property closable
76      * @type Boolean
77      * True if the tab is currently closable
78      */
79
80     scale: false,
81
82     position: 'top',
83
84     initComponent: function() {
85         var me = this;
86
87         me.addEvents(
88             /**
89              * @event activate
90              * Fired when the tab is activated.
91              * @param {Ext.tab.Tab} this
92              */
93             'activate',
94
95             /**
96              * @event deactivate
97              * Fired when the tab is deactivated.
98              * @param {Ext.tab.Tab} this
99              */
100             'deactivate',
101
102             /**
103              * @event beforeclose
104              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
105              * false from any listener to stop the close event being fired
106              * @param {Ext.tab.Tab} tab The Tab object
107              */
108             'beforeclose',
109
110             /**
111              * @event close
112              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
113              * @param {Ext.tab.Tab} tab The Tab object
114              */
115             'close'
116         );
117
118         me.callParent(arguments);
119
120         if (me.card) {
121             me.setCard(me.card);
122         }
123     },
124
125     /**
126      * @ignore
127      */
128     onRender: function() {
129         var me = this,
130             tabBar = me.up('tabbar'),
131             tabPanel = me.up('tabpanel');
132
133         me.addClsWithUI(me.position);
134
135         // Set all the state classNames, as they need to include the UI
136         // me.disabledCls = me.getClsWithUIs('disabled');
137
138         me.syncClosableUI();
139
140         // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
141         if (!me.minWidth) {
142             me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
143             if (!me.minWidth && tabPanel) {
144                 me.minWidth = tabPanel.minTabWidth;
145             }
146             if (me.minWidth && me.iconCls) {
147                 me.minWidth += 25;
148             }
149         }
150         if (!me.maxWidth) {
151             me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
152             if (!me.maxWidth && tabPanel) {
153                 me.maxWidth = tabPanel.maxTabWidth;
154             }
155         }
156
157         me.callParent(arguments);
158
159         if (me.active) {
160             me.activate(true);
161         }
162
163         me.syncClosableElements();
164
165         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
166             enter: me.onEnterKey,
167             del: me.onDeleteKey,
168             scope: me
169         });
170     },
171
172     // inherit docs
173     enable : function(silent) {
174         var me = this;
175
176         me.callParent(arguments);
177
178         me.removeClsWithUI(me.position + '-disabled');
179
180         return me;
181     },
182
183     // inherit docs
184     disable : function(silent) {
185         var me = this;
186
187         me.callParent(arguments);
188
189         me.addClsWithUI(me.position + '-disabled');
190
191         return me;
192     },
193
194     /**
195      * @ignore
196      */
197     onDestroy: function() {
198         var me = this;
199
200         if (me.closeEl) {
201             me.closeEl.un('click', Ext.EventManager.preventDefault);
202             me.closeEl = null;
203         }
204
205         Ext.destroy(me.keyNav);
206         delete me.keyNav;
207
208         me.callParent(arguments);
209     },
210
211     /**
212      * Sets the tab as either closable or not
213      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
214      * close button will appear on the tab)
215      */
216     setClosable: function(closable) {
217         var me = this;
218
219         // Closable must be true if no args
220         closable = (!arguments.length || !!closable);
221
222         if (me.closable != closable) {
223             me.closable = closable;
224
225             // set property on the user-facing item ('card'):
226             if (me.card) {
227                 me.card.closable = closable;
228             }
229
230             me.syncClosableUI();
231
232             if (me.rendered) {
233                 me.syncClosableElements();
234
235                 // Tab will change width to accommodate close icon
236                 me.doComponentLayout();
237                 if (me.ownerCt) {
238                     me.ownerCt.doLayout();
239                 }
240             }
241         }
242     },
243
244     /**
245      * This method ensures that the closeBtn element exists or not based on 'closable'.
246      * @private
247      */
248     syncClosableElements: function () {
249         var me = this;
250
251         if (me.closable) {
252             if (!me.closeEl) {
253                 me.closeEl = me.el.createChild({
254                     tag: 'a',
255                     cls: me.baseCls + '-close-btn',
256                     href: '#',
257                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
258                     title: me.closeText
259                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
260             }
261         } else {
262             var closeEl = me.closeEl;
263             if (closeEl) {
264                 closeEl.un('click', Ext.EventManager.preventDefault);
265                 closeEl.remove();
266                 me.closeEl = null;
267             }
268         }
269     },
270
271     /**
272      * This method ensures that the UI classes are added or removed based on 'closable'.
273      * @private
274      */
275     syncClosableUI: function () {
276         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
277
278         if (me.closable) {
279             me.addClsWithUI(classes);
280         } else {
281             me.removeClsWithUI(classes);
282         }
283     },
284
285     /**
286      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
287      * belongs to and would not need to be done by the developer
288      * @param {Ext.Component} card The card to set
289      */
290     setCard: function(card) {
291         var me = this;
292
293         me.card = card;
294         me.setText(me.title || card.title);
295         me.setIconCls(me.iconCls || card.iconCls);
296     },
297
298     /**
299      * @private
300      * Listener attached to click events on the Tab's close button
301      */
302     onCloseClick: function() {
303         var me = this;
304
305         if (me.fireEvent('beforeclose', me) !== false) {
306             if (me.tabBar) {
307                 if (me.tabBar.closeTab(me) === false) {
308                     // beforeclose on the panel vetoed the event, stop here
309                     return;
310                 }
311             } else {
312                 // if there's no tabbar, fire the close event
313                 me.fireEvent('close', me);
314             }
315         }
316     },
317
318     /**
319      * Fires the close event on the tab.
320      * @private
321      */
322     fireClose: function(){
323         this.fireEvent('close', this);
324     },
325
326     /**
327      * @private
328      */
329     onEnterKey: function(e) {
330         var me = this;
331
332         if (me.tabBar) {
333             me.tabBar.onClick(e, me.el);
334         }
335     },
336
337    /**
338      * @private
339      */
340     onDeleteKey: function(e) {
341         var me = this;
342
343         if (me.closable) {
344             me.onCloseClick();
345         }
346     },
347
348     // @private
349     activate : function(supressEvent) {
350         var me = this;
351
352         me.active = true;
353         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
354
355         if (supressEvent !== true) {
356             me.fireEvent('activate', me);
357         }
358     },
359
360     // @private
361     deactivate : function(supressEvent) {
362         var me = this;
363
364         me.active = false;
365         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
366
367         if (supressEvent !== true) {
368             me.fireEvent('deactivate', me);
369         }
370     }
371 });
372