Upgrade to ExtJS 4.0.2 - Released 06/09/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. 99% of the time 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. Defaults to 'x-tab-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. Defaults to 'x-tab-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). Defaults to true
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      * Defaults to 'Close Tab'.
66      */
67     closeText: 'Close Tab',
68
69     /**
70      * @property Boolean
71      * Read-only property indicating that this tab is currently active. This is NOT a public configuration.
72      */
73     active: false,
74
75     /**
76      * @property closable
77      * @type Boolean
78      * True if the tab is currently closable
79      */
80
81     scale: false,
82
83     position: 'top',
84     
85     initComponent: function() {
86         var me = this;
87
88         me.addEvents(
89             /**
90              * @event activate
91              * @param {Ext.tab.Tab} this
92              */
93             'activate',
94
95             /**
96              * @event deactivate
97              * @param {Ext.tab.Tab} this
98              */
99             'deactivate',
100
101             /**
102              * @event beforeclose
103              * Fires if the user clicks on the Tab's close button, but before the {@link #close} event is fired. Return
104              * false from any listener to stop the close event being fired
105              * @param {Ext.tab.Tab} tab The Tab object
106              */
107             'beforeclose',
108
109             /**
110              * @event beforeclose
111              * Fires to indicate that the tab is to be closed, usually because the user has clicked the close button.
112              * @param {Ext.tab.Tab} tab The Tab object
113              */
114             'close'
115         );
116         
117         me.callParent(arguments);
118
119         if (me.card) {
120             me.setCard(me.card);
121         }
122     },
123
124     /**
125      * @ignore
126      */
127     onRender: function() {
128         var me = this;
129         
130         me.addClsWithUI(me.position);
131         
132         // Set all the state classNames, as they need to include the UI
133         // me.disabledCls = me.getClsWithUIs('disabled');
134
135         me.syncClosableUI();
136
137         me.callParent(arguments);
138         
139         if (me.active) {
140             me.activate(true);
141         }
142
143         me.syncClosableElements();
144         
145         me.keyNav = Ext.create('Ext.util.KeyNav', me.el, {
146             enter: me.onEnterKey,
147             del: me.onDeleteKey,
148             scope: me
149         });
150     },
151     
152     // inherit docs
153     enable : function(silent) {
154         var me = this;
155
156         me.callParent(arguments);
157         
158         me.removeClsWithUI(me.position + '-disabled');
159
160         return me;
161     },
162
163     // inherit docs
164     disable : function(silent) {
165         var me = this;
166         
167         me.callParent(arguments);
168         
169         me.addClsWithUI(me.position + '-disabled');
170
171         return me;
172     },
173     
174     /**
175      * @ignore
176      */
177     onDestroy: function() {
178         var me = this;
179
180         if (me.closeEl) {
181             me.closeEl.un('click', Ext.EventManager.preventDefault);
182             me.closeEl = null;
183         }
184
185         Ext.destroy(me.keyNav);
186         delete me.keyNav;
187
188         me.callParent(arguments);
189     },
190
191     /**
192      * Sets the tab as either closable or not
193      * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab will be made closable (eg a
194      * close button will appear on the tab)
195      */
196     setClosable: function(closable) {
197         var me = this;
198
199         // Closable must be true if no args
200         closable = (!arguments.length || !!closable);
201
202         if (me.closable != closable) {
203             me.closable = closable;
204
205             // set property on the user-facing item ('card'):
206             if (me.card) {
207                 me.card.closable = closable;
208             }
209
210             me.syncClosableUI();
211
212             if (me.rendered) {
213                 me.syncClosableElements();
214
215                 // Tab will change width to accommodate close icon
216                 me.doComponentLayout();
217                 if (me.ownerCt) {
218                     me.ownerCt.doLayout();
219                 }
220             }
221         }
222     },
223
224     /**
225      * This method ensures that the closeBtn element exists or not based on 'closable'.
226      * @private
227      */
228     syncClosableElements: function () {
229         var me = this;
230
231         if (me.closable) {
232             if (!me.closeEl) {
233                 me.closeEl = me.el.createChild({
234                     tag: 'a',
235                     cls: me.baseCls + '-close-btn',
236                     href: '#',
237                     // html: me.closeText, // removed for EXTJSIV-1719, by rob@sencha.com
238                     title: me.closeText
239                 }).on('click', Ext.EventManager.preventDefault);  // mon ???
240             }
241         } else {
242             var closeEl = me.closeEl;
243             if (closeEl) {
244                 closeEl.un('click', Ext.EventManager.preventDefault);
245                 closeEl.remove();
246                 me.closeEl = null;
247             }
248         }
249     },
250
251     /**
252      * This method ensures that the UI classes are added or removed based on 'closable'.
253      * @private
254      */
255     syncClosableUI: function () {
256         var me = this, classes = [me.closableCls, me.closableCls + '-' + me.position];
257
258         if (me.closable) {
259             me.addClsWithUI(classes);
260         } else {
261             me.removeClsWithUI(classes);
262         }
263     },
264
265     /**
266      * Sets this tab's attached card. Usually this is handled automatically by the {@link Ext.tab.Panel} that this Tab
267      * belongs to and would not need to be done by the developer
268      * @param {Ext.Component} card The card to set
269      */
270     setCard: function(card) {
271         var me = this;
272
273         me.card = card;
274         me.setText(me.title || card.title);
275         me.setIconCls(me.iconCls || card.iconCls);
276     },
277
278     /**
279      * @private
280      * Listener attached to click events on the Tab's close button
281      */
282     onCloseClick: function() {
283         var me = this;
284
285         if (me.fireEvent('beforeclose', me) !== false) {
286             if (me.tabBar) {
287                 if (me.tabBar.closeTab(me) === false) {
288                     // beforeclose on the panel vetoed the event, stop here
289                     return;
290                 }
291             } else {
292                 // if there's no tabbar, fire the close event
293                 me.fireEvent('close', me);
294             }
295         }
296     },
297     
298     /**
299      * Fires the close event on the tab.
300      * @private
301      */
302     fireClose: function(){
303         this.fireEvent('close', this);
304     },
305     
306     /**
307      * @private
308      */
309     onEnterKey: function(e) {
310         var me = this;
311         
312         if (me.tabBar) {
313             me.tabBar.onClick(e, me.el);
314         }
315     },
316     
317    /**
318      * @private
319      */
320     onDeleteKey: function(e) {
321         var me = this;
322         
323         if (me.closable) {
324             me.onCloseClick();
325         }
326     },
327     
328     // @private
329     activate : function(supressEvent) {
330         var me = this;
331         
332         me.active = true;
333         me.addClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
334
335         if (supressEvent !== true) {
336             me.fireEvent('activate', me);
337         }
338     },
339
340     // @private
341     deactivate : function(supressEvent) {
342         var me = this;
343         
344         me.active = false;
345         me.removeClsWithUI([me.activeCls, me.position + '-' + me.activeCls]);
346         
347         if (supressEvent !== true) {
348             me.fireEvent('deactivate', me);
349         }
350     }
351 });
352