]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/_MenuBase.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / 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
5 "dojo/dom-attr",
6 "dojo/dom-class", // domClass.replace
7 "dojo/_base/lang", // lang.hitch
8 "dojo/mouse", // mouse.enter, mouse.leave
9 "dojo/on",
10 "dojo/window",
11 "./a11yclick",
12 "./popup",
13 "./registry",
14 "./_Widget",
15 "./_KeyNavContainer",
16 "./_TemplatedMixin"
17 ], function(array, declare, dom, domAttr, domClass, lang, mouse, on, winUtils,
18 a11yclick, pm, registry, _Widget, _KeyNavContainer, _TemplatedMixin){
19
20
21 // module:
22 // dijit/_MenuBase
23
24 return declare("dijit._MenuBase",
25 [_Widget, _TemplatedMixin, _KeyNavContainer],
26 {
27 // summary:
28 // Base class for Menu and MenuBar
29
30 // parentMenu: [readonly] Widget
31 // pointer to menu that displayed me
32 parentMenu: null,
33
34 // popupDelay: Integer
35 // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
36 popupDelay: 500,
37
38 // autoFocus: Boolean
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.
41 autoFocus: false,
42
43 childSelector: function(/*DOMNode*/ node){
44 // summary:
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.
47 // tags:
48 // protected
49
50 var widget = registry.byNode(node);
51 return node.parentNode == this.containerNode && widget && widget.focus;
52 },
53
54 postCreate: function(){
55 var self = this,
56 matches = typeof this.childSelector == "string" ? this.childSelector : lang.hitch(this, "childSelector");
57 this.own(
58 on(this.containerNode, on.selector(matches, mouse.enter), function(){
59 self.onItemHover(registry.byNode(this));
60 }),
61 on(this.containerNode, on.selector(matches, mouse.leave), function(){
62 self.onItemUnhover(registry.byNode(this));
63 }),
64 on(this.containerNode, on.selector(matches, a11yclick), function(evt){
65 self.onItemClick(registry.byNode(this), evt);
66 evt.stopPropagation();
67 evt.preventDefault();
68 })
69 );
70 this.inherited(arguments);
71 },
72
73 onExecute: function(){
74 // summary:
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.
79 // tags:
80 // protected
81 },
82
83 onCancel: function(/*Boolean*/ /*===== closeAll =====*/){
84 // summary:
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.
88 // tags:
89 // protected
90 },
91
92 _moveToPopup: function(/*Event*/ evt){
93 // summary:
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
96 // ancestor MenuBar
97 // tags:
98 // private
99
100 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
101 this.onItemClick(this.focusedChild, evt);
102 }else{
103 var topMenu = this._getTopMenu();
104 if(topMenu && topMenu._isMenuBar){
105 topMenu.focusNext();
106 }
107 }
108 },
109
110 _onPopupHover: function(/*Event*/ /*===== evt =====*/){
111 // summary:
112 // This handler is called when the mouse moves over the popup.
113 // tags:
114 // private
115
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);
124 }
125 parentMenu.focusedChild = this.currentPopup.from_item;
126 parentMenu.focusedChild._setSelected(true);
127 // cancel the pending close
128 this._stopPendingCloseTimer(this.currentPopup);
129 }
130 },
131
132 onItemHover: function(/*MenuItem*/ item){
133 // summary:
134 // Called when cursor is over a MenuItem.
135 // tags:
136 // protected
137
138 // Don't do anything unless user has "activated" the menu by:
139 // 1) clicking it
140 // 2) opening it from a parent menu (which automatically focuses it)
141 if(this.isActive){
142 this.focusChild(item);
143 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
144 this.hover_timer = this.defer("_openPopup", this.popupDelay);
145 }
146 }
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);
153 }
154 this._hoveredChild = item;
155
156 item._set("hovering", true);
157 },
158
159 _onChildBlur: function(item){
160 // summary:
161 // Called when a child MenuItem becomes inactive because focus
162 // has been removed from the MenuItem *and* it's descendant menus.
163 // tags:
164 // private
165 this._stopPopupTimer();
166 item._setSelected(false);
167 // Close all popups that are open and descendants of this menu
168 var itemPopup = item.popup;
169 if(itemPopup){
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;
175 }
176 pm.close(itemPopup); // this calls onClose
177 }, this.popupDelay);
178 }
179 },
180
181 onItemUnhover: function(/*MenuItem*/ item){
182 // summary:
183 // Callback fires when mouse exits a MenuItem
184 // tags:
185 // protected
186
187 if(this.isActive){
188 this._stopPopupTimer();
189 }
190 if(this._hoveredChild == item){ this._hoveredChild = null; }
191
192 item._set("hovering", false);
193 },
194
195 _stopPopupTimer: function(){
196 // summary:
197 // Cancels the popup timer because the user has stop hovering
198 // on the MenuItem, etc.
199 // tags:
200 // private
201 if(this.hover_timer){
202 this.hover_timer = this.hover_timer.remove();
203 }
204 },
205
206 _stopPendingCloseTimer: function(/*dijit/_WidgetBase*/ popup){
207 // summary:
208 // Cancels the pending-close timer because the close has been preempted
209 // tags:
210 // private
211 if(popup._pendingClose_timer){
212 popup._pendingClose_timer = popup._pendingClose_timer.remove();
213 }
214 },
215
216 _stopFocusTimer: function(){
217 // summary:
218 // Cancels the pending-focus timer because the menu was closed before focus occured
219 // tags:
220 // private
221 if(this._focus_timer){
222 this._focus_timer = this._focus_timer.remove();
223 }
224 },
225
226 _getTopMenu: function(){
227 // summary:
228 // Returns the top menu in this chain of Menus
229 // tags:
230 // private
231 for(var top=this; top.parentMenu; top=top.parentMenu);
232 return top;
233 },
234
235 onItemClick: function(/*dijit/_WidgetBase*/ item, /*Event*/ evt){
236 // summary:
237 // Handle clicks on an item.
238 // tags:
239 // private
240
241 // this can't be done in _onFocus since the _onFocus events occurs asynchronously
242 if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
243 this._markActive();
244 }
245
246 this.focusChild(item);
247
248 if(item.disabled){ return false; }
249
250 if(item.popup){
251 this._openPopup(evt.type == "keypress");
252 }else{
253 // before calling user defined handler, close hierarchy of menus
254 // and restore focus to place it was when menu was opened
255 this.onExecute();
256
257 // user defined handler for click
258 item._onClick ? item._onClick(evt) : item.onClick(evt);
259 }
260 },
261
262 _openPopup: function(/*Boolean*/ focus){
263 // summary:
264 // Open the popup to the side of/underneath the current menu item, and optionally focus first item
265 // tags:
266 // protected
267
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);
276 }
277 popup.parentMenu = this;
278 popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
279 var self = this;
280 pm.open({
281 parent: this,
282 popup: 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
292 },
293 onExecute: lang.hitch(this, "_cleanUp")
294 });
295
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
299 }
300
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;
308 this.focus();
309 }));
310 }
311 },
312
313 _markActive: function(){
314 // summary:
315 // Mark this menu's state as active.
316 // Called when this Menu gets focus from:
317 //
318 // 1. clicking it (mouse or via space/arrow key)
319 // 2. being opened by a parent menu.
320 //
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");
329 },
330
331 onOpen: function(/*Event*/ /*===== e =====*/){
332 // summary:
333 // Callback when this menu is opened.
334 // This is called by the popup manager as notification that the menu
335 // was opened.
336 // tags:
337 // private
338
339 this.isShowingNow = true;
340 this._markActive();
341 },
342
343 _markInactive: function(){
344 // summary:
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");
348 },
349
350 onClose: function(){
351 // summary:
352 // Callback when this menu is closed.
353 // This is called by the popup manager as notification that the menu
354 // was closed.
355 // tags:
356 // private
357
358 this._stopFocusTimer();
359 this._markInactive();
360 this.isShowingNow = false;
361 this.parentMenu = null;
362 },
363
364 _closeChild: function(){
365 // summary:
366 // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
367 // tags:
368 // private
369 this._stopPopupTimer();
370
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();
380 }
381 // Close all popups that are open and descendants of this menu
382 pm.close(this.currentPopup);
383 this.currentPopup = null;
384 }
385
386 if(this.focusedChild){ // unhighlight the focused item
387 this.focusedChild._setSelected(false);
388 this.onItemUnhover(this.focusedChild);
389 this.focusedChild = null;
390 }
391 },
392
393 _onItemFocus: function(/*MenuItem*/ item){
394 // summary:
395 // Called when child of this Menu gets focus from:
396 //
397 // 1. clicking it
398 // 2. tabbing into it
399 // 3. being opened by a parent menu.
400 //
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
404 }
405 },
406
407 _onBlur: function(){
408 // summary:
409 // Called when focus is moved away from this Menu and it's submenus.
410 // tags:
411 // protected
412 this._cleanUp();
413 this.inherited(arguments);
414 },
415
416 _cleanUp: function(){
417 // summary:
418 // Called when the user is done with this menu. Closes hierarchy of menus.
419 // tags:
420 // private
421
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();
425 }
426 }
427 });
428
429 });