]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * A base class for all menu items that require menu-related functionality such as click handling,\r | |
3 | * sub-menus, icons, etc.\r | |
4 | *\r | |
5 | * @example\r | |
6 | * Ext.create('Ext.menu.Menu', {\r | |
7 | * width: 100,\r | |
8 | * height: 100,\r | |
9 | * floating: false, // usually you want this set to True (default)\r | |
10 | * renderTo: Ext.getBody(), // usually rendered by it's containing component\r | |
11 | * items: [{\r | |
12 | * text: 'icon item',\r | |
13 | * iconCls: 'add16'\r | |
14 | * },{\r | |
15 | * text: 'text item'\r | |
16 | * },{\r | |
17 | * text: 'plain item',\r | |
18 | * plain: true\r | |
19 | * }]\r | |
20 | * });\r | |
21 | */\r | |
22 | Ext.define('Ext.menu.Item', {\r | |
23 | extend: 'Ext.Component',\r | |
24 | alias: 'widget.menuitem',\r | |
25 | alternateClassName: 'Ext.menu.TextItem',\r | |
26 | \r | |
27 | /**\r | |
28 | * @property {Boolean} isMenuItem\r | |
29 | * `true` in this class to identify an object as an instantiated Menu Item, or subclass thereof.\r | |
30 | */\r | |
31 | isMenuItem: true,\r | |
32 | \r | |
33 | mixins: [\r | |
34 | 'Ext.mixin.Queryable'\r | |
35 | ],\r | |
36 | \r | |
37 | /**\r | |
38 | * @property {Boolean} activated\r | |
39 | * Whether or not this item is currently activated\r | |
40 | */\r | |
41 | activated: false,\r | |
42 | \r | |
43 | /**\r | |
44 | * @property {Ext.menu.Menu} parentMenu\r | |
45 | * The parent Menu of this item.\r | |
46 | */\r | |
47 | \r | |
48 | /**\r | |
49 | * @cfg {String} activeCls\r | |
50 | * The CSS class added to the menu item when the item is focused.\r | |
51 | */\r | |
52 | activeCls: Ext.baseCSSPrefix + 'menu-item-active',\r | |
53 | \r | |
54 | /**\r | |
55 | * @cfg {Boolean} canActivate\r | |
56 | * Whether or not this menu item can be focused.\r | |
57 | * @deprecated 5.1.0 Use the {@link #focusable} config.\r | |
58 | */\r | |
59 | \r | |
60 | /**\r | |
61 | * @cfg {Number} clickHideDelay\r | |
62 | * The delay in milliseconds to wait before hiding the menu after clicking the menu item.\r | |
63 | * This only has an effect when `hideOnClick: true`.\r | |
64 | */\r | |
65 | clickHideDelay: 0,\r | |
66 | \r | |
67 | /**\r | |
68 | * @cfg {Boolean} destroyMenu\r | |
69 | * Whether or not to destroy any associated sub-menu when this item is destroyed.\r | |
70 | */\r | |
71 | destroyMenu: true,\r | |
72 | \r | |
73 | /**\r | |
74 | * @cfg {String} disabledCls\r | |
75 | * The CSS class added to the menu item when the item is disabled.\r | |
76 | */\r | |
77 | disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',\r | |
78 | \r | |
79 | /**\r | |
80 | * @cfg {String} [href='#']\r | |
81 | * The href attribute to use for the underlying anchor link.\r | |
82 | */\r | |
83 | \r | |
84 | /**\r | |
85 | * @cfg {String} hrefTarget\r | |
86 | * The target attribute to use for the underlying anchor link.\r | |
87 | */\r | |
88 | \r | |
89 | /**\r | |
90 | * @cfg {Boolean} hideOnClick\r | |
91 | * Whether to not to hide the owning menu when this item is clicked.\r | |
92 | */\r | |
93 | hideOnClick: true,\r | |
94 | \r | |
95 | /**\r | |
96 | * @cfg {String} [icon=Ext#BLANK_IMAGE_URL]\r | |
97 | * @inheritdoc Ext.panel.Header#icon\r | |
98 | */\r | |
99 | \r | |
100 | /**\r | |
101 | * @cfg {String} iconCls\r | |
102 | * @inheritdoc Ext.panel.Header#cfg-iconCls\r | |
103 | */\r | |
104 | \r | |
105 | /**\r | |
106 | * @cfg {Number/String} glyph\r | |
107 | * @inheritdoc Ext.panel.Header#glyph\r | |
108 | */\r | |
109 | \r | |
110 | /**\r | |
111 | * @cfg {Ext.menu.Menu/Object} menu\r | |
112 | * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}\r | |
113 | * which will act as a sub-menu to this item.\r | |
114 | */\r | |
115 | \r | |
116 | /**\r | |
117 | * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.\r | |
118 | */\r | |
119 | \r | |
120 | /**\r | |
121 | * @cfg {String} menuAlign\r | |
122 | * The default {@link Ext.util.Positionable#getAlignToXY Ext.util.Positionable.getAlignToXY} anchor position value for this\r | |
123 | * item's sub-menu relative to this item's position.\r | |
124 | */\r | |
125 | menuAlign: 'tl-tr?',\r | |
126 | \r | |
127 | /**\r | |
128 | * @cfg {Number} menuExpandDelay\r | |
129 | * The delay in milliseconds before this item's sub-menu expands after this item is moused over.\r | |
130 | */\r | |
131 | menuExpandDelay: 200,\r | |
132 | \r | |
133 | /**\r | |
134 | * @cfg {Number} menuHideDelay\r | |
135 | * The delay in milliseconds before this item's sub-menu hides after this item is moused out.\r | |
136 | */\r | |
137 | menuHideDelay: 200,\r | |
138 | \r | |
139 | /**\r | |
140 | * @cfg {Boolean} plain\r | |
141 | * Whether or not this item is plain text/html with no icon or visual submenu indication.\r | |
142 | */\r | |
143 | \r | |
144 | /**\r | |
145 | * @cfg {String/Object} tooltip\r | |
146 | * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or\r | |
147 | * QuickTips config object.\r | |
148 | */\r | |
149 | \r | |
150 | /**\r | |
151 | * @cfg {String} tooltipType\r | |
152 | * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.\r | |
153 | */\r | |
154 | tooltipType: 'qtip',\r | |
155 | \r | |
156 | focusable: true,\r | |
157 | ariaRole: 'menuitem',\r | |
158 | ariaEl: 'itemEl',\r | |
159 | \r | |
160 | baseCls: Ext.baseCSSPrefix + 'menu-item',\r | |
161 | arrowCls: Ext.baseCSSPrefix + 'menu-item-arrow',\r | |
162 | baseIconCls: Ext.baseCSSPrefix + 'menu-item-icon',\r | |
163 | textCls: Ext.baseCSSPrefix + 'menu-item-text',\r | |
164 | indentCls: Ext.baseCSSPrefix + 'menu-item-indent',\r | |
165 | indentNoSeparatorCls: Ext.baseCSSPrefix + 'menu-item-indent-no-separator',\r | |
166 | indentRightIconCls: Ext.baseCSSPrefix + 'menu-item-indent-right-icon',\r | |
167 | indentRightArrowCls: Ext.baseCSSPrefix + 'menu-item-indent-right-arrow',\r | |
168 | linkCls: Ext.baseCSSPrefix + 'menu-item-link',\r | |
169 | linkHrefCls: Ext.baseCSSPrefix + 'menu-item-link-href',\r | |
170 | \r | |
171 | childEls: [\r | |
172 | 'itemEl', 'iconEl', 'textEl', 'arrowEl'\r | |
173 | ],\r | |
174 | \r | |
175 | renderTpl:\r | |
176 | '<tpl if="plain">' +\r | |
177 | '{text}' +\r | |
178 | '<tpl else>' +\r | |
179 | '<a id="{id}-itemEl" data-ref="itemEl"' +\r | |
180 | ' class="{linkCls}<tpl if="hasHref"> {linkHrefCls}</tpl>{childElCls}"' +\r | |
181 | ' href="{href}" ' +\r | |
182 | '<tpl if="hrefTarget"> target="{hrefTarget}"</tpl>' +\r | |
183 | ' hidefocus="true"' +\r | |
184 | // For most browsers the text is already unselectable but Opera needs an explicit unselectable="on".\r | |
185 | ' unselectable="on"' +\r | |
186 | '<tpl if="tabIndex != null">' +\r | |
187 | ' tabindex="{tabIndex}"' +\r | |
188 | '</tpl>' +\r | |
189 | '<tpl foreach="ariaAttributes"> {$}="{.}"</tpl>' +\r | |
190 | '>' +\r | |
191 | '<span id="{id}-textEl" data-ref="textEl" class="{textCls} {textCls}-{ui} {indentCls}{childElCls}" unselectable="on">{text}</span>' +\r | |
192 | '<tpl if="hasIcon">' +\r | |
193 | '<div role="presentation" id="{id}-iconEl" data-ref="iconEl" class="{baseIconCls}-{ui} {baseIconCls}' +\r | |
194 | '{[values.rightIcon ? "-right" : ""]} {iconCls}' +\r | |
195 | '{childElCls} {glyphCls}" style="<tpl if="icon">background-image:url({icon});</tpl>' +\r | |
196 | '<tpl if="glyph && glyphFontFamily">font-family:{glyphFontFamily};</tpl>">' +\r | |
197 | '<tpl if="glyph">&#{glyph};</tpl>' +\r | |
198 | '</div>' +\r | |
199 | '</tpl>' +\r | |
200 | '<tpl if="showCheckbox">' +\r | |
201 | '<div role="presentation" id="{id}-checkEl" data-ref="checkEl" class="{baseIconCls}-{ui} {baseIconCls}' +\r | |
202 | '{[(values.hasIcon && !values.rightIcon) ? "-right" : ""]} ' +\r | |
203 | '{groupCls} {checkboxCls}{childElCls}">' +\r | |
204 | '</div>' +\r | |
205 | '</tpl>' +\r | |
206 | '<tpl if="hasMenu">' +\r | |
207 | '<div role="presentation" id="{id}-arrowEl" data-ref="arrowEl" class="{arrowCls} {arrowCls}-{ui}{childElCls}"></div>' +\r | |
208 | '</tpl>' +\r | |
209 | '</a>' +\r | |
210 | '</tpl>',\r | |
211 | \r | |
212 | maskOnDisable: false,\r | |
213 | \r | |
214 | iconAlign: 'left',\r | |
215 | \r | |
216 | /**\r | |
217 | * @cfg {String} text\r | |
218 | * The text/html to display in this item.\r | |
219 | */\r | |
220 | \r | |
221 | /**\r | |
222 | * @cfg {Function/String} handler\r | |
223 | * A function called when the menu item is clicked (can be used instead of {@link #click} event).\r | |
224 | * @cfg {Ext.menu.Item} handler.item The item that was clicked\r | |
225 | * @cfg {Ext.event.Event} handler.e The underlying {@link Ext.event.Event}.\r | |
226 | * @declarativeHandler\r | |
227 | */\r | |
228 | \r | |
229 | /**\r | |
230 | * @event activate\r | |
231 | * Fires when this item is activated\r | |
232 | * @param {Ext.menu.Item} item The activated item\r | |
233 | */\r | |
234 | \r | |
235 | /**\r | |
236 | * @event click\r | |
237 | * Fires when this item is clicked\r | |
238 | * @param {Ext.menu.Item} item The item that was clicked\r | |
239 | * @param {Ext.event.Event} e The underlying {@link Ext.event.Event}.\r | |
240 | */\r | |
241 | \r | |
242 | /**\r | |
243 | * @event deactivate\r | |
244 | * Fires when this item is deactivated\r | |
245 | * @param {Ext.menu.Item} item The deactivated item\r | |
246 | */\r | |
247 | \r | |
248 | /**\r | |
249 | * @event textchange\r | |
250 | * Fired when the item's text is changed by the {@link #setText} method.\r | |
251 | * @param {Ext.menu.Item} this\r | |
252 | * @param {String} oldText\r | |
253 | * @param {String} newText\r | |
254 | */\r | |
255 | \r | |
256 | /**\r | |
257 | * @event iconchange\r | |
258 | * Fired when the item's icon is changed by the {@link #setIcon} or {@link #setIconCls} methods.\r | |
259 | * @param {Ext.menu.Item} this\r | |
260 | * @param {String} oldIcon\r | |
261 | * @param {String} newIcon\r | |
262 | */\r | |
263 | \r | |
264 | initComponent: function() {\r | |
265 | var me = this,\r | |
266 | cls = me.cls ? [me.cls] : [],\r | |
267 | menu;\r | |
268 | \r | |
269 | // During deprecation period of canActivate config, copy it into focusable config.\r | |
270 | if (me.hasOwnProperty('canActivate')) {\r | |
271 | me.focusable = me.canActivate;\r | |
272 | }\r | |
273 | \r | |
274 | if (me.plain) {\r | |
275 | cls.push(Ext.baseCSSPrefix + 'menu-item-plain');\r | |
276 | }\r | |
277 | \r | |
278 | if (cls.length) {\r | |
279 | me.cls = cls.join(' ');\r | |
280 | }\r | |
281 | \r | |
282 | if (me.menu) {\r | |
283 | menu = me.menu;\r | |
284 | me.menu = null;\r | |
285 | me.setMenu(menu);\r | |
286 | }\r | |
287 | \r | |
288 | me.callParent(arguments);\r | |
289 | },\r | |
290 | \r | |
291 | canFocus: function() {\r | |
292 | var me = this;\r | |
293 | \r | |
294 | // This is an override of the implementation in Focusable.\r | |
295 | // We do not refuse focus if the Item is disabled.\r | |
296 | // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu\r | |
297 | // "Disabled menu items receive focus but have no action when Enter or Left Arrow/Right Arrow is pressed."\r | |
298 | // Test that deprecated canActivate config has not been set to false.\r | |
299 | return me.focusable && me.rendered && me.canActivate !== false &&\r | |
300 | !me.destroying && !me.destroyed &&\r | |
301 | me.isVisible(true);\r | |
302 | },\r | |
303 | \r | |
304 | onFocus: function(e) {\r | |
305 | var me = this;\r | |
306 | \r | |
307 | me.callParent([e]);\r | |
308 | \r | |
309 | if (!me.disabled) {\r | |
310 | if (!me.plain) {\r | |
311 | me.addCls(me.activeCls);\r | |
312 | }\r | |
313 | \r | |
314 | me.activated = true;\r | |
315 | if (me.hasListeners.activate) {\r | |
316 | me.fireEvent('activate', me);\r | |
317 | }\r | |
318 | }\r | |
319 | },\r | |
320 | \r | |
321 | onFocusLeave: function(e) {\r | |
322 | var me = this;\r | |
323 | \r | |
324 | me.callParent([e]);\r | |
325 | \r | |
326 | if (me.activated) {\r | |
327 | if (!me.plain) {\r | |
328 | me.removeCls(me.activeCls);\r | |
329 | }\r | |
330 | me.doHideMenu();\r | |
331 | me.activated = false;\r | |
332 | if (me.hasListeners.deactivate) {\r | |
333 | me.fireEvent('deactivate', me);\r | |
334 | }\r | |
335 | }\r | |
336 | },\r | |
337 | \r | |
338 | doHideMenu: function() {\r | |
339 | var menu = this.menu;\r | |
340 | \r | |
341 | this.cancelDeferExpand();\r | |
342 | if (menu && menu.isVisible()) {\r | |
343 | menu.hide();\r | |
344 | }\r | |
345 | },\r | |
346 | \r | |
347 | /**\r | |
348 | * @private\r | |
349 | * Hides the entire floating menu tree that we are within.\r | |
350 | * Walks up the refOwner axis hiding each Menu instance it find until it hits\r | |
351 | * a non-floating ancestor.\r | |
352 | */\r | |
353 | deferHideParentMenus: function() {\r | |
354 | for (var menu = this.getRefOwner(); menu && ((menu.isMenu && menu.floating) || menu.isMenuItem); menu = menu.getRefOwner()) {\r | |
355 | if (menu.isMenu) {\r | |
356 | menu.hide();\r | |
357 | }\r | |
358 | }\r | |
359 | },\r | |
360 | \r | |
361 | expandMenu: function(event, delay) {\r | |
362 | var me = this;\r | |
363 | \r | |
364 | if (me.activated && me.menu) {\r | |
365 | \r | |
366 | // hideOnClick makes no sense when there's a child menu\r | |
367 | me.hideOnClick = false;\r | |
368 | \r | |
369 | me.cancelDeferHide();\r | |
370 | \r | |
371 | // Allow configuration of zero to perform immediate expansion.\r | |
372 | delay = delay == null ? me.menuExpandDelay : delay;\r | |
373 | if (delay === 0) {\r | |
374 | me.doExpandMenu(event);\r | |
375 | } else {\r | |
376 | me.cancelDeferExpand();\r | |
377 | // Delay can't be 0 by this point\r | |
378 | me.expandMenuTimer = Ext.defer(me.doExpandMenu, delay, me, [event]);\r | |
379 | }\r | |
380 | }\r | |
381 | },\r | |
382 | \r | |
383 | doExpandMenu: function(clickEvent) {\r | |
384 | var me = this,\r | |
385 | menu = me.menu;\r | |
386 | \r | |
387 | if (!menu.isVisible()) {\r | |
388 | me.parentMenu.activeChild = menu;\r | |
389 | menu.ownerCmp = me;\r | |
390 | menu.parentMenu = me.parentMenu;\r | |
391 | menu.constrainTo = document.body;\r | |
392 | \r | |
393 | // Pointer-invoked menus do not auto focus, key invoked ones do.\r | |
394 | menu.autoFocus = !clickEvent || !clickEvent.pointerType;\r | |
395 | menu.showBy(me, me.menuAlign);\r | |
396 | }\r | |
397 | },\r | |
398 | \r | |
399 | getRefItems: function(deep) {\r | |
400 | var menu = this.menu,\r | |
401 | items;\r | |
402 | \r | |
403 | if (menu) {\r | |
404 | items = menu.getRefItems(deep);\r | |
405 | items.unshift(menu);\r | |
406 | }\r | |
407 | return items || [];\r | |
408 | },\r | |
409 | \r | |
410 | getValue: function () {\r | |
411 | return this.value;\r | |
412 | },\r | |
413 | \r | |
414 | hideMenu: function(delay) {\r | |
415 | var me = this;\r | |
416 | \r | |
417 | if (me.menu) {\r | |
418 | me.cancelDeferExpand();\r | |
419 | me.hideMenuTimer = Ext.defer(me.doHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me);\r | |
420 | }\r | |
421 | },\r | |
422 | \r | |
423 | onClick: function (e) {\r | |
424 | var me = this,\r | |
425 | clickHideDelay = me.clickHideDelay,\r | |
426 | browserEvent = e.browserEvent,\r | |
427 | clickResult, preventDefault;\r | |
428 | \r | |
429 | if (!me.href || me.disabled) {\r | |
430 | e.stopEvent();\r | |
431 | if (me.disabled) {\r | |
432 | return false;\r | |
433 | }\r | |
434 | }\r | |
435 | \r | |
436 | if (me.disabled || me.handlingClick) {\r | |
437 | return;\r | |
438 | }\r | |
439 | \r | |
440 | if (me.hideOnClick) {\r | |
441 | // on mobile webkit, when the menu item has an href, a longpress will \r | |
442 | // trigger the touch call-out menu to show. If this is the case, the tap \r | |
443 | // event object's browser event type will be 'touchcancel', and we do not \r | |
444 | // want to hide the menu.\r | |
445 | \r | |
446 | // items with submenus are activated by touchstart on mobile browsers, so\r | |
447 | // we cannot hide the menu on "tap"\r | |
448 | if (!clickHideDelay) {\r | |
449 | me.deferHideParentMenus();\r | |
450 | } else {\r | |
451 | me.deferHideParentMenusTimer = Ext.defer(me.deferHideParentMenus, clickHideDelay, me);\r | |
452 | }\r | |
453 | }\r | |
454 | \r | |
455 | // Click event may have destroyed the menu, don't do anything further\r | |
456 | clickResult = me.fireEvent('click', me, e);\r | |
457 | if (me.destroyed) {\r | |
458 | return;\r | |
459 | }\r | |
460 | \r | |
461 | if (clickResult !== false && me.handler) {\r | |
462 | Ext.callback(me.handler, me.scope, [me, e], 0, me);\r | |
463 | }\r | |
464 | \r | |
465 | // If there's an href, invoke dom.click() after we've fired the click event in case a click\r | |
466 | // listener wants to handle it.\r | |
467 | //\r | |
468 | // Note that we're having to do this because the key navigation code will blindly call stopEvent()\r | |
469 | // on all key events that it handles!\r | |
470 | //\r | |
471 | // But, we need to check the browser event object that was passed to the listeners to determine if\r | |
472 | // the default action has been prevented. If so, we don't want to honor the .href config.\r | |
473 | if (Ext.isIE9m) {\r | |
474 | // Here we need to invert the value since it's meaning is the opposite of defaultPrevented.\r | |
475 | preventDefault = browserEvent.returnValue === false ? true : false;\r | |
476 | } else {\r | |
477 | preventDefault = !!browserEvent.defaultPrevented;\r | |
478 | }\r | |
479 | \r | |
480 | // We only manually need to trigger the click event if it's come from a key event.\r | |
481 | if (me.href && e.type !== 'click' && !preventDefault) {\r | |
482 | me.handlingClick = true;\r | |
483 | me.itemEl.dom.click();\r | |
484 | me.handlingClick = false;\r | |
485 | }\r | |
486 | \r | |
487 | if (!me.hideOnClick) {\r | |
488 | me.focus();\r | |
489 | }\r | |
490 | return clickResult;\r | |
491 | },\r | |
492 | \r | |
493 | onRemoved: function() {\r | |
494 | var me = this;\r | |
495 | \r | |
496 | // Removing the active item, must deactivate it.\r | |
497 | if (me.activated && me.parentMenu.activeItem === me) {\r | |
498 | me.parentMenu.deactivateActiveItem();\r | |
499 | }\r | |
500 | me.callParent(arguments);\r | |
501 | me.parentMenu = me.ownerCmp = null;\r | |
502 | },\r | |
503 | \r | |
504 | /**\r | |
505 | * @private\r | |
506 | */\r | |
507 | beforeDestroy: function() {\r | |
508 | var me = this;\r | |
509 | if (me.rendered) {\r | |
510 | me.clearTip();\r | |
511 | }\r | |
512 | me.callParent();\r | |
513 | },\r | |
514 | \r | |
515 | onDestroy: function() {\r | |
516 | var me = this;\r | |
517 | \r | |
518 | me.cancelDeferExpand();\r | |
519 | me.cancelDeferHide();\r | |
520 | clearTimeout(me.deferHideParentMenusTimer);\r | |
521 | \r | |
522 | me.setMenu(null);\r | |
523 | me.callParent(arguments);\r | |
524 | },\r | |
525 | \r | |
526 | beforeRender: function() {\r | |
527 | var me = this,\r | |
528 | glyph = me.glyph,\r | |
529 | glyphFontFamily = Ext._glyphFontFamily,\r | |
530 | hasIcon = !!(me.icon || me.iconCls || glyph),\r | |
531 | hasMenu = !!me.menu,\r | |
532 | rightIcon = ((me.iconAlign === 'right') && !hasMenu),\r | |
533 | isCheckItem = me.isMenuCheckItem,\r | |
534 | indentCls = [],\r | |
535 | ownerCt = me.ownerCt,\r | |
536 | isOwnerPlain = ownerCt.plain,\r | |
537 | glyphParts;\r | |
538 | \r | |
539 | if (me.plain) {\r | |
540 | me.ariaEl = 'el';\r | |
541 | }\r | |
542 | \r | |
543 | me.callParent();\r | |
544 | \r | |
545 | if (hasIcon) {\r | |
546 | if (hasMenu && me.showCheckbox) {\r | |
547 | // nowhere to put the icon, menu arrow on one side, checkbox on the other.\r | |
548 | // TODO: maybe put the icon or checkbox next to the arrow?\r | |
549 | hasIcon = false;\r | |
550 | }\r | |
551 | }\r | |
552 | \r | |
553 | if (typeof glyph === 'string') {\r | |
554 | glyphParts = glyph.split('@');\r | |
555 | glyph = glyphParts[0];\r | |
556 | glyphFontFamily = glyphParts[1];\r | |
557 | }\r | |
558 | \r | |
559 | if (!isOwnerPlain || (hasIcon && !rightIcon) || isCheckItem) {\r | |
560 | if (ownerCt.showSeparator && !isOwnerPlain) {\r | |
561 | indentCls.push(me.indentCls);\r | |
562 | } else {\r | |
563 | indentCls.push(me.indentNoSeparatorCls);\r | |
564 | }\r | |
565 | }\r | |
566 | \r | |
567 | if (hasMenu) {\r | |
568 | indentCls.push(me.indentRightArrowCls);\r | |
569 | } else if (hasIcon && (rightIcon || isCheckItem)) {\r | |
570 | indentCls.push(me.indentRightIconCls);\r | |
571 | }\r | |
572 | \r | |
573 | Ext.applyIf(me.renderData, {\r | |
574 | hasHref: !!me.href,\r | |
575 | href: me.href || '#',\r | |
576 | hrefTarget: me.hrefTarget,\r | |
577 | icon: me.icon,\r | |
578 | iconCls: me.iconCls,\r | |
579 | glyph: glyph,\r | |
580 | glyphCls: glyph ? Ext.baseCSSPrefix + 'menu-item-glyph' : undefined,\r | |
581 | glyphFontFamily: glyphFontFamily,\r | |
582 | hasIcon: hasIcon,\r | |
583 | hasMenu: hasMenu,\r | |
584 | indent: !isOwnerPlain || hasIcon || isCheckItem,\r | |
585 | isCheckItem: isCheckItem,\r | |
586 | rightIcon: rightIcon,\r | |
587 | plain: me.plain,\r | |
588 | text: me.text,\r | |
589 | arrowCls: me.arrowCls,\r | |
590 | baseIconCls: me.baseIconCls,\r | |
591 | textCls: me.textCls,\r | |
592 | indentCls: indentCls.join(' '),\r | |
593 | linkCls: me.linkCls,\r | |
594 | linkHrefCls: me.linkHrefCls,\r | |
595 | groupCls: me.group ? me.groupCls : '',\r | |
596 | tabIndex: me.tabIndex\r | |
597 | });\r | |
598 | },\r | |
599 | \r | |
600 | onRender: function() {\r | |
601 | var me = this;\r | |
602 | \r | |
603 | me.callParent(arguments);\r | |
604 | \r | |
605 | if (me.tooltip) {\r | |
606 | me.setTooltip(me.tooltip, true);\r | |
607 | }\r | |
608 | },\r | |
609 | \r | |
610 | /**\r | |
611 | * Get the attached sub-menu for this item.\r | |
612 | * @return {Ext.menu.Menu} The sub-menu. `null` if it doesn't exist.\r | |
613 | */\r | |
614 | getMenu: function() {\r | |
615 | return this.menu || null;\r | |
616 | },\r | |
617 | \r | |
618 | /**\r | |
619 | * Set a child menu for this item. See the {@link #cfg-menu} configuration.\r | |
620 | * @param {Ext.menu.Menu/Object} menu A menu, or menu configuration. null may be\r | |
621 | * passed to remove the menu.\r | |
622 | * @param {Boolean} [destroyMenu] True to destroy any existing menu. False to\r | |
623 | * prevent destruction. If not specified, the {@link #destroyMenu} configuration\r | |
624 | * will be used.\r | |
625 | */\r | |
626 | setMenu: function(menu, destroyMenu) {\r | |
627 | var me = this,\r | |
628 | oldMenu = me.menu,\r | |
629 | arrowEl = me.arrowEl,\r | |
630 | ariaDom = me.ariaEl.dom,\r | |
631 | ariaAttr, instanced;\r | |
632 | \r | |
633 | if (oldMenu) {\r | |
634 | oldMenu.ownerCmp = oldMenu.parentMenu = null;\r | |
635 | \r | |
636 | if (destroyMenu === true || (destroyMenu !== false && me.destroyMenu)) {\r | |
637 | Ext.destroy(oldMenu);\r | |
638 | }\r | |
639 | \r | |
640 | if (ariaDom) {\r | |
641 | ariaDom.removeAttribute('aria-haspopup');\r | |
642 | ariaDom.removeAttribute('aria-owns');\r | |
643 | }\r | |
644 | else {\r | |
645 | ariaAttr = (me.ariaRenderAttributes || (me.ariaRenderAttributes = {}));\r | |
646 | \r | |
647 | delete ariaAttr['aria-haspopup'];\r | |
648 | delete ariaAttr['aria-owns'];\r | |
649 | }\r | |
650 | }\r | |
651 | \r | |
652 | if (menu) {\r | |
653 | instanced = menu.isMenu;\r | |
654 | menu = me.menu = Ext.menu.Manager.get(menu, {\r | |
655 | ownerCmp: me,\r | |
656 | focusOnToFront: false\r | |
657 | });\r | |
658 | // We need to forcibly set this here because we could be passed\r | |
659 | // an existing menu, which means the config above won't get applied\r | |
660 | // during creation.\r | |
661 | menu.setOwnerCmp(me, instanced);\r | |
662 | \r | |
663 | if (ariaDom) {\r | |
664 | ariaDom.setAttribute('aria-haspopup', true);\r | |
665 | ariaDom.setAttribute('aria-owns', menu.id);\r | |
666 | }\r | |
667 | else {\r | |
668 | ariaAttr = (me.ariaRenderAttributes || (me.ariaRenderAttributes = {}));\r | |
669 | \r | |
670 | ariaAttr['aria-haspopup'] = true;\r | |
671 | ariaAttr['aria-owns'] = menu.id;\r | |
672 | }\r | |
673 | }\r | |
674 | else {\r | |
675 | menu = me.menu = null;\r | |
676 | }\r | |
677 | \r | |
678 | if (menu && me.rendered && !me.destroying && arrowEl) {\r | |
679 | arrowEl[menu ? 'addCls' : 'removeCls'](me.arrowCls);\r | |
680 | }\r | |
681 | },\r | |
682 | \r | |
683 | /**\r | |
684 | * Sets the {@link #click} handler of this item\r | |
685 | * @param {Function} fn The handler function\r | |
686 | * @param {Object} [scope] The scope of the handler function\r | |
687 | */\r | |
688 | setHandler: function(fn, scope) {\r | |
689 | this.handler = fn || null;\r | |
690 | this.scope = scope;\r | |
691 | },\r | |
692 | \r | |
693 | /**\r | |
694 | * Sets the {@link #icon} on this item.\r | |
695 | * @param {String} icon The new icon\r | |
696 | */\r | |
697 | setIcon: function(icon){\r | |
698 | var iconEl = this.iconEl,\r | |
699 | oldIcon = this.icon;\r | |
700 | if (iconEl) {\r | |
701 | iconEl.src = icon || Ext.BLANK_IMAGE_URL;\r | |
702 | }\r | |
703 | this.icon = icon;\r | |
704 | this.fireEvent('iconchange', this, oldIcon, icon);\r | |
705 | },\r | |
706 | \r | |
707 | /**\r | |
708 | * Sets the {@link #iconCls} of this item\r | |
709 | * @param {String} iconCls The CSS class to set to {@link #iconCls}\r | |
710 | */\r | |
711 | setIconCls: function(iconCls) {\r | |
712 | var me = this,\r | |
713 | iconEl = me.iconEl,\r | |
714 | oldCls = me.iconCls;\r | |
715 | \r | |
716 | if (iconEl) {\r | |
717 | if (me.iconCls) {\r | |
718 | iconEl.removeCls(me.iconCls);\r | |
719 | }\r | |
720 | \r | |
721 | if (iconCls) {\r | |
722 | iconEl.addCls(iconCls);\r | |
723 | }\r | |
724 | }\r | |
725 | \r | |
726 | me.iconCls = iconCls;\r | |
727 | me.fireEvent('iconchange', me, oldCls, iconCls);\r | |
728 | },\r | |
729 | \r | |
730 | /**\r | |
731 | * Sets the {@link #text} of this item\r | |
732 | * @param {String} text The {@link #text}\r | |
733 | */\r | |
734 | setText: function(text) {\r | |
735 | var me = this,\r | |
736 | el = me.textEl || me.el,\r | |
737 | oldText = me.text;\r | |
738 | \r | |
739 | me.text = text;\r | |
740 | \r | |
741 | if (me.rendered) {\r | |
742 | el.setHtml(text || '');\r | |
743 | me.updateLayout();\r | |
744 | }\r | |
745 | me.fireEvent('textchange', me, oldText, text);\r | |
746 | },\r | |
747 | \r | |
748 | getTipAttr: function(){\r | |
749 | return this.tooltipType === 'qtip' ? 'data-qtip' : 'title';\r | |
750 | },\r | |
751 | \r | |
752 | /**\r | |
753 | * @private\r | |
754 | */\r | |
755 | clearTip: function() {\r | |
756 | if (Ext.quickTipsActive && Ext.isObject(this.tooltip)) {\r | |
757 | Ext.tip.QuickTipManager.unregister(this.itemEl);\r | |
758 | }\r | |
759 | },\r | |
760 | \r | |
761 | /**\r | |
762 | * Sets the tooltip for this menu item.\r | |
763 | *\r | |
764 | * @param {String/Object} tooltip This may be:\r | |
765 | *\r | |
766 | * - **String** : A string to be used as innerHTML (html tags are accepted) to show in a tooltip\r | |
767 | * - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.\r | |
768 | *\r | |
769 | * @return {Ext.menu.Item} this\r | |
770 | */\r | |
771 | setTooltip: function(tooltip, initial) {\r | |
772 | var me = this;\r | |
773 | \r | |
774 | if (me.rendered) {\r | |
775 | if (!initial) {\r | |
776 | me.clearTip();\r | |
777 | }\r | |
778 | \r | |
779 | if (Ext.quickTipsActive && Ext.isObject(tooltip)) {\r | |
780 | Ext.tip.QuickTipManager.register(Ext.apply({\r | |
781 | target: me.itemEl.id\r | |
782 | },\r | |
783 | tooltip));\r | |
784 | me.tooltip = tooltip;\r | |
785 | } else {\r | |
786 | me.itemEl.dom.setAttribute(me.getTipAttr(), tooltip);\r | |
787 | }\r | |
788 | } else {\r | |
789 | me.tooltip = tooltip;\r | |
790 | }\r | |
791 | \r | |
792 | return me;\r | |
793 | },\r | |
794 | \r | |
795 | privates: {\r | |
796 | cancelDeferExpand: function() {\r | |
797 | window.clearTimeout(this.expandMenuTimer);\r | |
798 | },\r | |
799 | \r | |
800 | cancelDeferHide: function(){\r | |
801 | window.clearTimeout(this.hideMenuTimer);\r | |
802 | },\r | |
803 | \r | |
804 | getFocusEl: function() {\r | |
805 | return this.plain ? this.el : this.itemEl;\r | |
806 | }\r | |
807 | }\r | |
808 | });\r |