]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/_MenuBase.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / _MenuBase.js.uncompressed.js
1 define("dijit/_MenuBase", [
2 "./popup",
3 "dojo/window",
4 "./_Widget",
5 "./_KeyNavContainer",
6 "./_TemplatedMixin",
7 "dojo/_base/declare", // declare
8 "dojo/dom", // dom.isDescendant domClass.replace
9 "dojo/dom-attr",
10 "dojo/dom-class", // domClass.replace
11 "dojo/_base/lang", // lang.hitch
12 "dojo/_base/array" // array.indexOf
13 ], function(pm, winUtils, _Widget, _KeyNavContainer, _TemplatedMixin,
14 declare, dom, domAttr, domClass, lang, array){
15
16 /*=====
17 var _Widget = dijit._Widget;
18 var _TemplatedMixin = dijit._TemplatedMixin;
19 var _KeyNavContainer = dijit._KeyNavContainer;
20 =====*/
21
22 // module:
23 // dijit/_MenuBase
24 // summary:
25 // Base class for Menu and MenuBar
26
27 return declare("dijit._MenuBase",
28 [_Widget, _TemplatedMixin, _KeyNavContainer],
29 {
30 // summary:
31 // Base class for Menu and MenuBar
32
33 // parentMenu: [readonly] Widget
34 // pointer to menu that displayed me
35 parentMenu: null,
36
37 // popupDelay: Integer
38 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
39 popupDelay: 500,
40
41 onExecute: function(){
42 // summary:
43 // Attach point for notification about when a menu item has been executed.
44 // This is an internal mechanism used for Menus to signal to their parent to
45 // close them, because they are about to execute the onClick handler. In
46 // general developers should not attach to or override this method.
47 // tags:
48 // protected
49 },
50
51 onCancel: function(/*Boolean*/ /*===== closeAll =====*/){
52 // summary:
53 // Attach point for notification about when the user cancels the current menu
54 // This is an internal mechanism used for Menus to signal to their parent to
55 // close them. In general developers should not attach to or override this method.
56 // tags:
57 // protected
58 },
59
60 _moveToPopup: function(/*Event*/ evt){
61 // summary:
62 // This handles the right arrow key (left arrow key on RTL systems),
63 // which will either open a submenu, or move to the next item in the
64 // ancestor MenuBar
65 // tags:
66 // private
67
68 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
69 this.focusedChild._onClick(evt);
70 }else{
71 var topMenu = this._getTopMenu();
72 if(topMenu && topMenu._isMenuBar){
73 topMenu.focusNext();
74 }
75 }
76 },
77
78 _onPopupHover: function(/*Event*/ /*===== evt =====*/){
79 // summary:
80 // This handler is called when the mouse moves over the popup.
81 // tags:
82 // private
83
84 // if the mouse hovers over a menu popup that is in pending-close state,
85 // then stop the close operation.
86 // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
87 if(this.currentPopup && this.currentPopup._pendingClose_timer){
88 var parentMenu = this.currentPopup.parentMenu;
89 // highlight the parent menu item pointing to this popup
90 if(parentMenu.focusedChild){
91 parentMenu.focusedChild._setSelected(false);
92 }
93 parentMenu.focusedChild = this.currentPopup.from_item;
94 parentMenu.focusedChild._setSelected(true);
95 // cancel the pending close
96 this._stopPendingCloseTimer(this.currentPopup);
97 }
98 },
99
100 onItemHover: function(/*MenuItem*/ item){
101 // summary:
102 // Called when cursor is over a MenuItem.
103 // tags:
104 // protected
105
106 // Don't do anything unless user has "activated" the menu by:
107 // 1) clicking it
108 // 2) opening it from a parent menu (which automatically focuses it)
109 if(this.isActive){
110 this.focusChild(item);
111 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
112 this.hover_timer = setTimeout(lang.hitch(this, "_openPopup"), this.popupDelay);
113 }
114 }
115 // if the user is mixing mouse and keyboard navigation,
116 // then the menu may not be active but a menu item has focus,
117 // but it's not the item that the mouse just hovered over.
118 // To avoid both keyboard and mouse selections, use the latest.
119 if(this.focusedChild){
120 this.focusChild(item);
121 }
122 this._hoveredChild = item;
123 },
124
125 _onChildBlur: function(item){
126 // summary:
127 // Called when a child MenuItem becomes inactive because focus
128 // has been removed from the MenuItem *and* it's descendant menus.
129 // tags:
130 // private
131 this._stopPopupTimer();
132 item._setSelected(false);
133 // Close all popups that are open and descendants of this menu
134 var itemPopup = item.popup;
135 if(itemPopup){
136 this._stopPendingCloseTimer(itemPopup);
137 itemPopup._pendingClose_timer = setTimeout(function(){
138 itemPopup._pendingClose_timer = null;
139 if(itemPopup.parentMenu){
140 itemPopup.parentMenu.currentPopup = null;
141 }
142 pm.close(itemPopup); // this calls onClose
143 }, this.popupDelay);
144 }
145 },
146
147 onItemUnhover: function(/*MenuItem*/ item){
148 // summary:
149 // Callback fires when mouse exits a MenuItem
150 // tags:
151 // protected
152
153 if(this.isActive){
154 this._stopPopupTimer();
155 }
156 if(this._hoveredChild == item){ this._hoveredChild = null; }
157 },
158
159 _stopPopupTimer: function(){
160 // summary:
161 // Cancels the popup timer because the user has stop hovering
162 // on the MenuItem, etc.
163 // tags:
164 // private
165 if(this.hover_timer){
166 clearTimeout(this.hover_timer);
167 this.hover_timer = null;
168 }
169 },
170
171 _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
172 // summary:
173 // Cancels the pending-close timer because the close has been preempted
174 // tags:
175 // private
176 if(popup._pendingClose_timer){
177 clearTimeout(popup._pendingClose_timer);
178 popup._pendingClose_timer = null;
179 }
180 },
181
182 _stopFocusTimer: function(){
183 // summary:
184 // Cancels the pending-focus timer because the menu was closed before focus occured
185 // tags:
186 // private
187 if(this._focus_timer){
188 clearTimeout(this._focus_timer);
189 this._focus_timer = null;
190 }
191 },
192
193 _getTopMenu: function(){
194 // summary:
195 // Returns the top menu in this chain of Menus
196 // tags:
197 // private
198 for(var top=this; top.parentMenu; top=top.parentMenu);
199 return top;
200 },
201
202 onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
203 // summary:
204 // Handle clicks on an item.
205 // tags:
206 // private
207
208 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
209 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
210 this._markActive();
211 }
212
213 this.focusChild(item);
214
215 if(item.disabled){ return false; }
216
217 if(item.popup){
218 this._openPopup();
219 }else{
220 // before calling user defined handler, close hierarchy of menus
221 // and restore focus to place it was when menu was opened
222 this.onExecute();
223
224 // user defined handler for click
225 item.onClick(evt);
226 }
227 },
228
229 _openPopup: function(){
230 // summary:
231 // Open the popup to the side of/underneath the current menu item
232 // tags:
233 // protected
234
235 this._stopPopupTimer();
236 var from_item = this.focusedChild;
237 if(!from_item){ return; } // the focused child lost focus since the timer was started
238 var popup = from_item.popup;
239 if(popup.isShowingNow){ return; }
240 if(this.currentPopup){
241 this._stopPendingCloseTimer(this.currentPopup);
242 pm.close(this.currentPopup);
243 }
244 popup.parentMenu = this;
245 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
246 var self = this;
247 pm.open({
248 parent: this,
249 popup: popup,
250 around: from_item.domNode,
251 orient: this._orient || ["after", "before"],
252 onCancel: function(){ // called when the child menu is canceled
253 // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
254 // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
255 self.focusChild(from_item); // put focus back on my node
256 self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
257 from_item._setSelected(true); // oops, _cleanUp() deselected the item
258 self.focusedChild = from_item; // and unset focusedChild
259 },
260 onExecute: lang.hitch(this, "_cleanUp")
261 });
262
263 this.currentPopup = popup;
264 // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
265 popup.connect(popup.domNode, "onmouseenter", lang.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
266
267 if(popup.focus){
268 // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
269 // if the cursor happens to collide with the popup, it will generate an onmouseover event
270 // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
271 // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
272 popup._focus_timer = setTimeout(lang.hitch(popup, function(){
273 this._focus_timer = null;
274 this.focus();
275 }), 0);
276 }
277 },
278
279 _markActive: function(){
280 // summary:
281 // Mark this menu's state as active.
282 // Called when this Menu gets focus from:
283 // 1) clicking it (mouse or via space/arrow key)
284 // 2) being opened by a parent menu.
285 // This is not called just from mouse hover.
286 // Focusing a menu via TAB does NOT automatically set isActive
287 // since TAB is a navigation operation and not a selection one.
288 // For Windows apps, pressing the ALT key focuses the menubar
289 // menus (similar to TAB navigation) but the menu is not active
290 // (ie no dropdown) until an item is clicked.
291 this.isActive = true;
292 domClass.replace(this.domNode, "dijitMenuActive", "dijitMenuPassive");
293 },
294
295 onOpen: function(/*Event*/ /*===== e =====*/){
296 // summary:
297 // Callback when this menu is opened.
298 // This is called by the popup manager as notification that the menu
299 // was opened.
300 // tags:
301 // private
302
303 this.isShowingNow = true;
304 this._markActive();
305 },
306
307 _markInactive: function(){
308 // summary:
309 // Mark this menu's state as inactive.
310 this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
311 domClass.replace(this.domNode, "dijitMenuPassive", "dijitMenuActive");
312 },
313
314 onClose: function(){
315 // summary:
316 // Callback when this menu is closed.
317 // This is called by the popup manager as notification that the menu
318 // was closed.
319 // tags:
320 // private
321
322 this._stopFocusTimer();
323 this._markInactive();
324 this.isShowingNow = false;
325 this.parentMenu = null;
326 },
327
328 _closeChild: function(){
329 // summary:
330 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
331 // tags:
332 // private
333 this._stopPopupTimer();
334
335 if(this.currentPopup){
336 // If focus is on a descendant MenuItem then move focus to me,
337 // because IE doesn't like it when you display:none a node with focus,
338 // and also so keyboard users don't lose control.
339 // Likely, immediately after a user defined onClick handler will move focus somewhere
340 // else, like a Dialog.
341 if(array.indexOf(this._focusManager.activeStack, this.id) >= 0){
342 domAttr.set(this.focusedChild.focusNode, "tabIndex", this.tabIndex);
343 this.focusedChild.focusNode.focus();
344 }
345 // Close all popups that are open and descendants of this menu
346 pm.close(this.currentPopup);
347 this.currentPopup = null;
348 }
349
350 if(this.focusedChild){ // unhighlight the focused item
351 this.focusedChild._setSelected(false);
352 this.focusedChild._onUnhover();
353 this.focusedChild = null;
354 }
355 },
356
357 _onItemFocus: function(/*MenuItem*/ item){
358 // summary:
359 // Called when child of this Menu gets focus from:
360 // 1) clicking it
361 // 2) tabbing into it
362 // 3) being opened by a parent menu.
363 // This is not called just from mouse hover.
364 if(this._hoveredChild && this._hoveredChild != item){
365 this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
366 }
367 },
368
369 _onBlur: function(){
370 // summary:
371 // Called when focus is moved away from this Menu and it's submenus.
372 // tags:
373 // protected
374 this._cleanUp();
375 this.inherited(arguments);
376 },
377
378 _cleanUp: function(){
379 // summary:
380 // Called when the user is done with this menu. Closes hierarchy of menus.
381 // tags:
382 // private
383
384 this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
385 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
386 this._markInactive();
387 }
388 }
389 });
390
391 });