Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / menu / Manager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.menu.Manager
17  * Provides a common registry of all menus on a page.
18  * @singleton
19  */
20 Ext.define('Ext.menu.Manager', {
21     singleton: true,
22     requires: [
23         'Ext.util.MixedCollection',
24         'Ext.util.KeyMap'
25     ],
26     alternateClassName: 'Ext.menu.MenuMgr',
27
28     uses: ['Ext.menu.Menu'],
29
30     menus: {},
31     groups: {},
32     attached: false,
33     lastShow: new Date(),
34
35     init: function() {
36         var me = this;
37         
38         me.active = Ext.create('Ext.util.MixedCollection');
39         Ext.getDoc().addKeyListener(27, function() {
40             if (me.active.length > 0) {
41                 me.hideAll();
42             }
43         }, me);
44     },
45
46     /**
47      * Hides all menus that are currently visible
48      * @return {Boolean} success True if any active menus were hidden.
49      */
50     hideAll: function() {
51         var active = this.active,
52             c;
53         if (active && active.length > 0) {
54             c = active.clone();
55             c.each(function(m) {
56                 m.hide();
57             });
58             return true;
59         }
60         return false;
61     },
62
63     onHide: function(m) {
64         var me = this,
65             active = me.active;
66         active.remove(m);
67         if (active.length < 1) {
68             Ext.getDoc().un('mousedown', me.onMouseDown, me);
69             me.attached = false;
70         }
71     },
72
73     onShow: function(m) {
74         var me = this,
75             active   = me.active,
76             last     = active.last(),
77             attached = me.attached,
78             menuEl   = m.getEl(),
79             zIndex;
80
81         me.lastShow = new Date();
82         active.add(m);
83         if (!attached) {
84             Ext.getDoc().on('mousedown', me.onMouseDown, me);
85             me.attached = true;
86         }
87         m.toFront();
88     },
89
90     onBeforeHide: function(m) {
91         if (m.activeChild) {
92             m.activeChild.hide();
93         }
94         if (m.autoHideTimer) {
95             clearTimeout(m.autoHideTimer);
96             delete m.autoHideTimer;
97         }
98     },
99
100     onBeforeShow: function(m) {
101         var active = this.active,
102             parentMenu = m.parentMenu;
103             
104         active.remove(m);
105         if (!parentMenu && !m.allowOtherMenus) {
106             this.hideAll();
107         }
108         else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
109             parentMenu.activeChild.hide();
110         }
111     },
112
113     // private
114     onMouseDown: function(e) {
115         var me = this,
116             active = me.active,
117             lastShow = me.lastShow,
118             target = e.target;
119
120         if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
121             me.hideAll();
122             // in IE, if we mousedown on a focusable element, the focus gets cancelled and the focus event is never
123             // fired on the element, so we'll focus it here
124             if (Ext.isIE && Ext.fly(target).focusable()) {
125                 target.focus();
126             }
127         }
128     },
129
130     // private
131     register: function(menu) {
132         var me = this;
133
134         if (!me.active) {
135             me.init();
136         }
137
138         if (menu.floating) {
139             me.menus[menu.id] = menu;
140             menu.on({
141                 beforehide: me.onBeforeHide,
142                 hide: me.onHide,
143                 beforeshow: me.onBeforeShow,
144                 show: me.onShow,
145                 scope: me
146             });
147         }
148     },
149
150     /**
151      * Returns a {@link Ext.menu.Menu} object
152      * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
153      * be used to generate and return a new Menu this.
154      * @return {Ext.menu.Menu} The specified menu, or null if none are found
155      */
156     get: function(menu) {
157         var menus = this.menus;
158         
159         if (typeof menu == 'string') { // menu id
160             if (!menus) {  // not initialized, no menus to return
161                 return null;
162             }
163             return menus[menu];
164         } else if (menu.isMenu) {  // menu instance
165             return menu;
166         } else if (Ext.isArray(menu)) { // array of menu items
167             return Ext.create('Ext.menu.Menu', {items:menu});
168         } else { // otherwise, must be a config
169             return Ext.ComponentManager.create(menu, 'menu');
170         }
171     },
172
173     // private
174     unregister: function(menu) {
175         var me = this,
176             menus = me.menus,
177             active = me.active;
178
179         delete menus[menu.id];
180         active.remove(menu);
181         menu.un({
182             beforehide: me.onBeforeHide,
183             hide: me.onHide,
184             beforeshow: me.onBeforeShow,
185             show: me.onShow,
186             scope: me
187         });
188     },
189
190     // private
191     registerCheckable: function(menuItem) {
192         var groups  = this.groups,
193             groupId = menuItem.group;
194
195         if (groupId) {
196             if (!groups[groupId]) {
197                 groups[groupId] = [];
198             }
199
200             groups[groupId].push(menuItem);
201         }
202     },
203
204     // private
205     unregisterCheckable: function(menuItem) {
206         var groups  = this.groups,
207             groupId = menuItem.group;
208
209         if (groupId) {
210             Ext.Array.remove(groups[groupId], menuItem);
211         }
212     },
213
214     onCheckChange: function(menuItem, state) {
215         var groups  = this.groups,
216             groupId = menuItem.group,
217             i       = 0,
218             group, ln, curr;
219
220         if (groupId && state) {
221             group = groups[groupId];
222             ln = group.length;
223             for (; i < ln; i++) {
224                 curr = group[i];
225                 if (curr != menuItem) {
226                     curr.setChecked(false);
227                 }
228             }
229         }
230     }
231 });