]> git.wh0rd.org Git - 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 });