]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/_MenuBase.js.uncompressed.js
1 define("dijit/_MenuBase", [
2 "dojo/_base/array", // array.indexOf
3 "dojo/_base/declare", // declare
4 "dojo/dom", // dom.isDescendant domClass.replace
6 "dojo/dom-class", // domClass.replace
7 "dojo/_base/lang", // lang.hitch
8 "dojo/mouse", // mouse.enter, mouse.leave
17 ], function(array
, declare
, dom
, domAttr
, domClass
, lang
, mouse
, on
, winUtils
,
18 a11yclick
, pm
, registry
, _Widget
, _KeyNavContainer
, _TemplatedMixin
){
24 return declare("dijit._MenuBase",
25 [_Widget
, _TemplatedMixin
, _KeyNavContainer
],
28 // Base class for Menu and MenuBar
30 // parentMenu: [readonly] Widget
31 // pointer to menu that displayed me
34 // popupDelay: Integer
35 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
39 // A toggle to control whether or not a Menu gets focused when opened as a drop down from a MenuBar
40 // or DropDownButton/ComboButton. Note though that it always get focused when opened via the keyboard.
43 childSelector: function(/*DOMNode*/ node
){
45 // Selector (passed to on.selector()) used to identify MenuItem child widgets, but exclude inert children
46 // like MenuSeparator. If subclass overrides to a string (ex: "> *"), the subclass must require dojo/query.
50 var widget
= registry
.byNode(node
);
51 return node
.parentNode
== this.containerNode
&& widget
&& widget
.focus
;
54 postCreate: function(){
56 matches
= typeof this.childSelector
== "string" ? this.childSelector
: lang
.hitch(this, "childSelector");
58 on(this.containerNode
, on
.selector(matches
, mouse
.enter
), function(){
59 self
.onItemHover(registry
.byNode(this));
61 on(this.containerNode
, on
.selector(matches
, mouse
.leave
), function(){
62 self
.onItemUnhover(registry
.byNode(this));
64 on(this.containerNode
, on
.selector(matches
, a11yclick
), function(evt
){
65 self
.onItemClick(registry
.byNode(this), evt
);
66 evt
.stopPropagation();
70 this.inherited(arguments
);
73 onExecute: function(){
75 // Attach point for notification about when a menu item has been executed.
76 // This is an internal mechanism used for Menus to signal to their parent to
77 // close them, because they are about to execute the onClick handler. In
78 // general developers should not attach to or override this method.
83 onCancel: function(/*Boolean*/ /*===== closeAll =====*/){
85 // Attach point for notification about when the user cancels the current menu
86 // This is an internal mechanism used for Menus to signal to their parent to
87 // close them. In general developers should not attach to or override this method.
92 _moveToPopup: function(/*Event*/ evt
){
94 // This handles the right arrow key (left arrow key on RTL systems),
95 // which will either open a submenu, or move to the next item in the
100 if(this.focusedChild
&& this.focusedChild
.popup
&& !this.focusedChild
.disabled
){
101 this.onItemClick(this.focusedChild
, evt
);
103 var topMenu
= this._getTopMenu();
104 if(topMenu
&& topMenu
._isMenuBar
){
110 _onPopupHover: function(/*Event*/ /*===== evt =====*/){
112 // This handler is called when the mouse moves over the popup.
116 // if the mouse hovers over a menu popup that is in pending-close state,
117 // then stop the close operation.
118 // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
119 if(this.currentPopup
&& this.currentPopup
._pendingClose_timer
){
120 var parentMenu
= this.currentPopup
.parentMenu
;
121 // highlight the parent menu item pointing to this popup
122 if(parentMenu
.focusedChild
){
123 parentMenu
.focusedChild
._setSelected(false);
125 parentMenu
.focusedChild
= this.currentPopup
.from_item
;
126 parentMenu
.focusedChild
._setSelected(true);
127 // cancel the pending close
128 this._stopPendingCloseTimer(this.currentPopup
);
132 onItemHover: function(/*MenuItem*/ item
){
134 // Called when cursor is over a MenuItem.
138 // Don't do anything unless user has "activated" the menu by:
140 // 2) opening it from a parent menu (which automatically focuses it)
142 this.focusChild(item
);
143 if(this.focusedChild
.popup
&& !this.focusedChild
.disabled
&& !this.hover_timer
){
144 this.hover_timer
= this.defer("_openPopup", this.popupDelay
);
147 // if the user is mixing mouse and keyboard navigation,
148 // then the menu may not be active but a menu item has focus,
149 // but it's not the item that the mouse just hovered over.
150 // To avoid both keyboard and mouse selections, use the latest.
151 if(this.focusedChild
){
152 this.focusChild(item
);
154 this._hoveredChild
= item
;
156 item
._set("hovering", true);
159 _onChildBlur: function(item
){
161 // Called when a child MenuItem becomes inactive because focus
162 // has been removed from the MenuItem *and* it's descendant menus.
165 this._stopPopupTimer();
166 item
._setSelected(false);
167 // Close all popups that are open and descendants of this menu
168 var itemPopup
= item
.popup
;
170 this._stopPendingCloseTimer(itemPopup
);
171 itemPopup
._pendingClose_timer
= this.defer(function(){
172 itemPopup
._pendingClose_timer
= null;
173 if(itemPopup
.parentMenu
){
174 itemPopup
.parentMenu
.currentPopup
= null;
176 pm
.close(itemPopup
); // this calls onClose
181 onItemUnhover: function(/*MenuItem*/ item
){
183 // Callback fires when mouse exits a MenuItem
188 this._stopPopupTimer();
190 if(this._hoveredChild
== item
){ this._hoveredChild
= null; }
192 item
._set("hovering", false);
195 _stopPopupTimer: function(){
197 // Cancels the popup timer because the user has stop hovering
198 // on the MenuItem, etc.
201 if(this.hover_timer
){
202 this.hover_timer
= this.hover_timer
.remove();
206 _stopPendingCloseTimer: function(/*dijit/_WidgetBase*/ popup){
208 // Cancels the pending-close timer because the close has been preempted
211 if(popup._pendingClose_timer){
212 popup._pendingClose_timer = popup._pendingClose_timer.remove();
216 _stopFocusTimer: function(){
218 // Cancels the pending-focus timer because the menu was closed before focus occured
221 if(this._focus_timer){
222 this._focus_timer = this._focus_timer.remove();
226 _getTopMenu: function(){
228 // Returns the top menu in this chain of Menus
231 for(var top=this; top.parentMenu; top=top.parentMenu);
235 onItemClick: function(/*dijit/_WidgetBase*/ item, /*Event*/ evt
){
237 // Handle clicks on an item.
241 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
242 if(typeof this.isShowingNow
== 'undefined'){ // non-popup menu
246 this.focusChild(item
);
248 if(item
.disabled
){ return false; }
251 this._openPopup(evt
.type
== "keypress");
253 // before calling user defined handler, close hierarchy of menus
254 // and restore focus to place it was when menu was opened
257 // user defined handler for click
258 item
._onClick
? item
._onClick(evt
) : item
.onClick(evt
);
262 _openPopup: function(/*Boolean*/ focus
){
264 // Open the popup to the side of/underneath the current menu item, and optionally focus first item
268 this._stopPopupTimer();
269 var from_item
= this.focusedChild
;
270 if(!from_item
){ return; } // the focused child lost focus since the timer was started
271 var popup
= from_item
.popup
;
272 if(!popup
.isShowingNow
){
273 if(this.currentPopup
){
274 this._stopPendingCloseTimer(this.currentPopup
);
275 pm
.close(this.currentPopup
);
277 popup
.parentMenu
= this;
278 popup
.from_item
= from_item
; // helps finding the parent item that should be focused for this popup
283 around
: from_item
.domNode
,
284 orient
: this._orient
|| ["after", "before"],
285 onCancel: function(){ // called when the child menu is canceled
286 // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
287 // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
288 self
.focusChild(from_item
); // put focus back on my node
289 self
._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
290 from_item
._setSelected(true); // oops, _cleanUp() deselected the item
291 self
.focusedChild
= from_item
; // and unset focusedChild
293 onExecute
: lang
.hitch(this, "_cleanUp")
296 this.currentPopup
= popup
;
297 // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
298 popup
.connect(popup
.domNode
, "onmouseenter", lang
.hitch(self
, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
301 if(focus
&& popup
.focus
){
302 // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), then focus the popup.
303 // If the cursor happens to collide with the popup, it will generate an onmouseover event
304 // even though the mouse wasn't moved. Use defer() to call popup.focus so that
305 // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
306 popup
._focus_timer
= this.defer(lang
.hitch(popup
, function(){
307 this._focus_timer
= null;
313 _markActive: function(){
315 // Mark this menu's state as active.
316 // Called when this Menu gets focus from:
318 // 1. clicking it (mouse or via space/arrow key)
319 // 2. being opened by a parent menu.
321 // This is not called just from mouse hover.
322 // Focusing a menu via TAB does NOT automatically set isActive
323 // since TAB is a navigation operation and not a selection one.
324 // For Windows apps, pressing the ALT key focuses the menubar
325 // menus (similar to TAB navigation) but the menu is not active
326 // (ie no dropdown) until an item is clicked.
327 this.isActive
= true;
328 domClass
.replace(this.domNode
, "dijitMenuActive", "dijitMenuPassive");
331 onOpen: function(/*Event*/ /*===== e =====*/){
333 // Callback when this menu is opened.
334 // This is called by the popup manager as notification that the menu
339 this.isShowingNow
= true;
343 _markInactive: function(){
345 // Mark this menu's state as inactive.
346 this.isActive
= false; // don't do this in _onBlur since the state is pending-close until we get here
347 domClass
.replace(this.domNode
, "dijitMenuPassive", "dijitMenuActive");
352 // Callback when this menu is closed.
353 // This is called by the popup manager as notification that the menu
358 this._stopFocusTimer();
359 this._markInactive();
360 this.isShowingNow
= false;
361 this.parentMenu
= null;
364 _closeChild: function(){
366 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
369 this._stopPopupTimer();
371 if(this.currentPopup
){
372 // If focus is on a descendant MenuItem then move focus to me,
373 // because IE doesn't like it when you display:none a node with focus,
374 // and also so keyboard users don't lose control.
375 // Likely, immediately after a user defined onClick handler will move focus somewhere
376 // else, like a Dialog.
377 if(array
.indexOf(this._focusManager
.activeStack
, this.id
) >= 0){
378 domAttr
.set(this.focusedChild
.focusNode
, "tabIndex", this.tabIndex
);
379 this.focusedChild
.focusNode
.focus();
381 // Close all popups that are open and descendants of this menu
382 pm
.close(this.currentPopup
);
383 this.currentPopup
= null;
386 if(this.focusedChild
){ // unhighlight the focused item
387 this.focusedChild
._setSelected(false);
388 this.onItemUnhover(this.focusedChild
);
389 this.focusedChild
= null;
393 _onItemFocus: function(/*MenuItem*/ item
){
395 // Called when child of this Menu gets focus from:
398 // 2. tabbing into it
399 // 3. being opened by a parent menu.
401 // This is not called just from mouse hover.
402 if(this._hoveredChild
&& this._hoveredChild
!= item
){
403 this.onItemUnhover(this._hoveredChild
); // any previous mouse movement is trumped by focus selection
409 // Called when focus is moved away from this Menu and it's submenus.
413 this.inherited(arguments
);
416 _cleanUp: function(){
418 // Called when the user is done with this menu. Closes hierarchy of menus.
422 this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
423 if(typeof this.isShowingNow
== 'undefined'){ // non-popup menu doesn't call onClose
424 this._markInactive();