Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / menu / Item.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  * @class Ext.menu.Item
17  * @extends Ext.Component
18  *
19  * A base class for all menu items that require menu-related functionality such as click handling,
20  * sub-menus, icons, etc.
21  *
22  * {@img Ext.menu.Menu/Ext.menu.Menu.png Ext.menu.Menu component}
23  *
24  * __Example Usage:__
25  *
26  *     Ext.create('Ext.menu.Menu', {
27  *         width: 100,
28  *         height: 100,
29  *         floating: false,  // usually you want this set to True (default)
30  *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
31  *         items: [{
32  *             text: 'icon item',
33  *             iconCls: 'add16'
34  *         },{
35  *             text: 'text item',
36  *         },{
37  *             text: 'plain item',
38  *             plain: true
39  *         }]
40  *     });
41  *
42  */
43 Ext.define('Ext.menu.Item', {
44     extend: 'Ext.Component',
45     alias: 'widget.menuitem',
46     alternateClassName: 'Ext.menu.TextItem',
47
48     /**
49      * @property {Boolean} activated
50      * Whether or not this item is currently activated
51      */
52
53     /**
54      * @cfg {String} activeCls
55      * The CSS class added to the menu item when the item is activated (focused/mouseover).
56      * Defaults to `Ext.baseCSSPrefix + 'menu-item-active'`.
57      * @markdown
58      */
59     activeCls: Ext.baseCSSPrefix + 'menu-item-active',
60
61     /**
62      * @cfg {String} ariaRole @hide
63      */
64     ariaRole: 'menuitem',
65
66     /**
67      * @cfg {Boolean} canActivate
68      * Whether or not this menu item can be activated when focused/mouseovered. Defaults to `true`.
69      * @markdown
70      */
71     canActivate: true,
72
73     /**
74      * @cfg {Number} clickHideDelay
75      * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
76      * This only has an effect when `hideOnClick: true`. Defaults to `1`.
77      * @markdown
78      */
79     clickHideDelay: 1,
80
81     /**
82      * @cfg {Boolean} destroyMenu
83      * Whether or not to destroy any associated sub-menu when this item is destroyed. Defaults to `true`.
84      */
85     destroyMenu: true,
86
87     /**
88      * @cfg {String} disabledCls
89      * The CSS class added to the menu item when the item is disabled.
90      * Defaults to `Ext.baseCSSPrefix + 'menu-item-disabled'`.
91      * @markdown
92      */
93     disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
94
95     /**
96      * @cfg {String} href
97      * The href attribute to use for the underlying anchor link. Defaults to `#`.
98      * @markdown
99      */
100
101      /**
102       * @cfg {String} hrefTarget
103       * The target attribute to use for the underlying anchor link. Defaults to `undefined`.
104       * @markdown
105       */
106
107     /**
108      * @cfg {Boolean} hideOnClick
109      * Whether to not to hide the owning menu when this item is clicked. Defaults to `true`.
110      * @markdown
111      */
112     hideOnClick: true,
113
114     /**
115      * @cfg {String} icon
116      * The path to an icon to display in this item. Defaults to `Ext.BLANK_IMAGE_URL`.
117      * @markdown
118      */
119
120     /**
121      * @cfg {String} iconCls
122      * A CSS class that specifies a `background-image` to use as the icon for this item. Defaults to `undefined`.
123      * @markdown
124      */
125
126     isMenuItem: true,
127
128     /**
129      * @cfg {Mixed} menu
130      * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
131      * which will act as a sub-menu to this item.
132      * @markdown
133      * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
134      */
135
136     /**
137      * @cfg {String} menuAlign
138      * The default {@link Ext.core.Element#getAlignToXY Ext.Element.getAlignToXY} anchor position value for this
139      * item's sub-menu relative to this item's position. Defaults to `'tl-tr?'`.
140      * @markdown
141      */
142     menuAlign: 'tl-tr?',
143
144     /**
145      * @cfg {Number} menuExpandDelay
146      * The delay in milliseconds before this item's sub-menu expands after this item is moused over. Defaults to `200`.
147      * @markdown
148      */
149     menuExpandDelay: 200,
150
151     /**
152      * @cfg {Number} menuHideDelay
153      * The delay in milliseconds before this item's sub-menu hides after this item is moused out. Defaults to `200`.
154      * @markdown
155      */
156     menuHideDelay: 200,
157
158     /**
159      * @cfg {Boolean} plain
160      * Whether or not this item is plain text/html with no icon or visual activation. Defaults to `false`.
161      * @markdown
162      */
163
164     renderTpl: [
165         '<tpl if="plain">',
166             '{text}',
167         '</tpl>',
168         '<tpl if="!plain">',
169             '<a class="' + Ext.baseCSSPrefix + 'menu-item-link" href="{href}" <tpl if="hrefTarget">target="{hrefTarget}"</tpl> hidefocus="true" unselectable="on">',
170                 '<img src="{icon}" class="' + Ext.baseCSSPrefix + 'menu-item-icon {iconCls}" />',
171                 '<span class="' + Ext.baseCSSPrefix + 'menu-item-text" <tpl if="menu">style="margin-right: 17px;"</tpl> >{text}</span>',
172                 '<tpl if="menu">',
173                     '<img src="' + Ext.BLANK_IMAGE_URL + '" class="' + Ext.baseCSSPrefix + 'menu-item-arrow" />',
174                 '</tpl>',
175             '</a>',
176         '</tpl>'
177     ],
178
179     maskOnDisable: false,
180
181     /**
182      * @cfg {String} text
183      * The text/html to display in this item. Defaults to `undefined`.
184      * @markdown
185      */
186
187     activate: function() {
188         var me = this;
189
190         if (!me.activated && me.canActivate && me.rendered && !me.isDisabled() && me.isVisible()) {
191             me.el.addCls(me.activeCls);
192             me.focus();
193             me.activated = true;
194             me.fireEvent('activate', me);
195         }
196     },
197
198     blur: function() {
199         this.$focused = false;
200         this.callParent(arguments);
201     },
202
203     deactivate: function() {
204         var me = this;
205
206         if (me.activated) {
207             me.el.removeCls(me.activeCls);
208             me.blur();
209             me.hideMenu();
210             me.activated = false;
211             me.fireEvent('deactivate', me);
212         }
213     },
214
215     deferExpandMenu: function() {
216         var me = this;
217
218         if (!me.menu.rendered || !me.menu.isVisible()) {
219             me.parentMenu.activeChild = me.menu;
220             me.menu.parentItem = me;
221             me.menu.parentMenu = me.menu.ownerCt = me.parentMenu;
222             me.menu.showBy(me, me.menuAlign);
223         }
224     },
225
226     deferHideMenu: function() {
227         if (this.menu.isVisible()) {
228             this.menu.hide();
229         }
230     },
231
232     deferHideParentMenus: function() {
233         Ext.menu.Manager.hideAll();
234     },
235
236     expandMenu: function(delay) {
237         var me = this;
238
239         if (me.menu) {
240             clearTimeout(me.hideMenuTimer);
241             if (delay === 0) {
242                 me.deferExpandMenu();
243             } else {
244                 me.expandMenuTimer = Ext.defer(me.deferExpandMenu, Ext.isNumber(delay) ? delay : me.menuExpandDelay, me);
245             }
246         }
247     },
248
249     focus: function() {
250         this.$focused = true;
251         this.callParent(arguments);
252     },
253
254     getRefItems: function(deep){
255         var menu = this.menu,
256             items;
257
258         if (menu) {
259             items = menu.getRefItems(deep);
260             items.unshift(menu);
261         }
262         return items || [];
263     },
264
265     hideMenu: function(delay) {
266         var me = this;
267
268         if (me.menu) {
269             clearTimeout(me.expandMenuTimer);
270             me.hideMenuTimer = Ext.defer(me.deferHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);
271         }
272     },
273
274     initComponent: function() {
275         var me = this,
276             prefix = Ext.baseCSSPrefix,
277             cls = [prefix + 'menu-item'];
278
279         me.addEvents(
280             /**
281              * @event activate
282              * Fires when this item is activated
283              * @param {Ext.menu.Item} item The activated item
284              */
285             'activate',
286
287             /**
288              * @event click
289              * Fires when this item is clicked
290              * @param {Ext.menu.Item} item The item that was clicked
291              * @param {Ext.EventObject} e The underyling {@link Ext.EventObject}.
292              */
293             'click',
294
295             /**
296              * @event deactivate
297              * Fires when this tiem is deactivated
298              * @param {Ext.menu.Item} item The deactivated item
299              */
300             'deactivate'
301         );
302
303         if (me.plain) {
304             cls.push(prefix + 'menu-item-plain');
305         }
306
307         if (me.cls) {
308             cls.push(me.cls);
309         }
310
311         me.cls = cls.join(' ');
312
313         if (me.menu) {
314             me.menu = Ext.menu.Manager.get(me.menu);
315         }
316
317         me.callParent(arguments);
318     },
319
320     onClick: function(e) {
321         var me = this;
322
323         if (!me.href) {
324             e.stopEvent();
325         }
326
327         if (me.disabled) {
328             return;
329         }
330
331         if (me.hideOnClick) {
332             me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, me.clickHideDelay, me);
333         }
334
335         Ext.callback(me.handler, me.scope || me, [me, e]);
336         me.fireEvent('click', me, e);
337
338         if (!me.hideOnClick) {
339             me.focus();
340         }
341     },
342
343     onDestroy: function() {
344         var me = this;
345
346         clearTimeout(me.expandMenuTimer);
347         clearTimeout(me.hideMenuTimer);
348         clearTimeout(me.deferHideParentMenusTimer);
349
350         if (me.menu) {
351             delete me.menu.parentItem;
352             delete me.menu.parentMenu;
353             delete me.menu.ownerCt;
354             if (me.destroyMenu !== false) {
355                 me.menu.destroy();
356             }
357         }
358         me.callParent(arguments);
359     },
360
361     onRender: function(ct, pos) {
362         var me = this,
363             prefix = '.' + Ext.baseCSSPrefix;
364
365         Ext.applyIf(me.renderData, {
366             href: me.href || '#',
367             hrefTarget: me.hrefTarget,
368             icon: me.icon || Ext.BLANK_IMAGE_URL,
369             iconCls: me.iconCls + (me.checkChangeDisabled ? ' ' + me.disabledCls : ''),
370             menu: Ext.isDefined(me.menu),
371             plain: me.plain,
372             text: me.text
373         });
374
375         Ext.applyIf(me.renderSelectors, {
376             itemEl: prefix + 'menu-item-link',
377             iconEl: prefix + 'menu-item-icon',
378             textEl: prefix + 'menu-item-text',
379             arrowEl: prefix + 'menu-item-arrow'
380         });
381
382         me.callParent(arguments);
383     },
384
385     /**
386      * Sets the {@link #click} handler of this item
387      * @param {Function} fn The handler function
388      * @param {Object} scope (optional) The scope of the handler function
389      */
390     setHandler: function(fn, scope) {
391         this.handler = fn || null;
392         this.scope = scope;
393     },
394
395     /**
396      * Sets the {@link #iconCls} of this item
397      * @param {String} iconCls The CSS class to set to {@link #iconCls}
398      */
399     setIconCls: function(iconCls) {
400         var me = this;
401
402         if (me.iconEl) {
403             if (me.iconCls) {
404                 me.iconEl.removeCls(me.iconCls);
405             }
406
407             if (iconCls) {
408                 me.iconEl.addCls(iconCls);
409             }
410         }
411
412         me.iconCls = iconCls;
413     },
414
415     /**
416      * Sets the {@link #text} of this item
417      * @param {String} text The {@link #text}
418      */
419     setText: function(text) {
420         var me = this,
421             el = me.textEl || me.el;
422
423         me.text = text;
424
425         if (me.rendered) {
426             el.update(text || '');
427             // cannot just call doComponentLayout due to stretchmax
428             me.ownerCt.redoComponentLayout();
429         }
430     }
431 });
432