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