source: Dev/branches/rest-dojo-ui/client/dijit/_MenuBase.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 11.9 KB
Line 
1define([
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
27return 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});
Note: See TracBrowser for help on using the repository browser.