2 * Ext JS Library 2.2.1
\r
3 * Copyright(c) 2006-2009, Ext JS, LLC.
\r
4 * licensing@extjs.com
\r
6 * http://extjs.com/license
\r
10 * @class Ext.menu.Menu
\r
11 * @extends Ext.util.Observable
\r
12 * A menu object. This is the container to which you add all other menu items. Menu can also serve as a base class
\r
13 * when you want a specialized menu based off of another component (like {@link Ext.menu.DateMenu} for example).
\r
15 * Creates a new Menu
\r
16 * @param {Object} config Configuration options
\r
18 Ext.menu.Menu = function(config){
\r
19 if(Ext.isArray(config)){
\r
20 config = {items:config};
\r
22 Ext.apply(this, config);
\r
23 this.id = this.id || Ext.id();
\r
27 * Fires before this menu is displayed
\r
28 * @param {Ext.menu.Menu} this
\r
33 * Fires before this menu is hidden
\r
34 * @param {Ext.menu.Menu} this
\r
39 * Fires after this menu is displayed
\r
40 * @param {Ext.menu.Menu} this
\r
45 * Fires after this menu is hidden
\r
46 * @param {Ext.menu.Menu} this
\r
51 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
\r
52 * @param {Ext.menu.Menu} this
\r
53 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
54 * @param {Ext.EventObject} e
\r
59 * Fires when the mouse is hovering over this menu
\r
60 * @param {Ext.menu.Menu} this
\r
61 * @param {Ext.EventObject} e
\r
62 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
67 * Fires when the mouse exits this menu
\r
68 * @param {Ext.menu.Menu} this
\r
69 * @param {Ext.EventObject} e
\r
70 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
75 * Fires when a menu item contained in this menu is clicked
\r
76 * @param {Ext.menu.BaseItem} baseItem The BaseItem that was clicked
\r
77 * @param {Ext.EventObject} e
\r
81 Ext.menu.MenuMgr.register(this);
\r
82 Ext.menu.Menu.superclass.constructor.call(this);
\r
83 var mis = this.items;
\r
85 * A MixedCollection of this Menu's items
\r
87 * @type Ext.util.MixedCollection
\r
90 this.items = new Ext.util.MixedCollection();
\r
92 this.add.apply(this, mis);
\r
96 Ext.extend(Ext.menu.Menu, Ext.util.Observable, {
\r
98 * @cfg {Object} defaults
\r
99 * A config object that will be applied to all items added to this container either via the {@link #items}
\r
100 * config or via the {@link #add} method. The defaults config can contain any number of
\r
101 * name/value property pairs to be added to each item, and should be valid for the types of items
\r
102 * being added to the menu.
\r
105 * @cfg {Mixed} items
\r
106 * An array of items to be added to this menu. See {@link #add} for a list of valid item types.
\r
109 * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
\r
113 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
\r
114 * for bottom-right shadow (defaults to "sides")
\r
118 * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of
\r
119 * this menu (defaults to "tl-tr?")
\r
121 subMenuAlign : "tl-tr?",
\r
123 * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu
\r
124 * relative to its element of origin (defaults to "tl-bl?")
\r
126 defaultAlign : "tl-bl?",
\r
128 * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
\r
130 allowOtherMenus : false,
\r
132 * @cfg {Boolean} ignoreParentClicks True to ignore clicks on any item in this menu that is a parent item (displays
\r
133 * a submenu) so that the submenu is not dismissed when clicking the parent item (defaults to false).
\r
135 ignoreParentClicks : false,
\r
141 createEl : function(){
\r
142 return new Ext.Layer({
\r
144 shadow:this.shadow,
\r
146 parentEl: this.parentEl || document.body,
\r
152 render : function(){
\r
156 var el = this.el = this.createEl();
\r
159 this.keyNav = new Ext.menu.MenuNav(this);
\r
162 el.addClass("x-menu-plain");
\r
165 el.addClass(this.cls);
\r
167 // generic focus element
\r
168 this.focusEl = el.createChild({
\r
169 tag: "a", cls: "x-menu-focus", href: "#", onclick: "return false;", tabIndex:"-1"
\r
171 var ul = el.createChild({tag: "ul", cls: "x-menu-list"});
\r
172 ul.on("click", this.onClick, this);
\r
173 ul.on("mouseover", this.onMouseOver, this);
\r
174 ul.on("mouseout", this.onMouseOut, this);
\r
175 this.items.each(function(item){
\r
176 var li = document.createElement("li");
\r
177 li.className = "x-menu-list-item";
\r
178 ul.dom.appendChild(li);
\r
179 item.render(li, this);
\r
186 autoWidth : function(){
\r
187 var el = this.el, ul = this.ul;
\r
191 var w = this.width;
\r
194 }else if(Ext.isIE){
\r
195 el.setWidth(this.minWidth);
\r
196 var t = el.dom.offsetWidth; // force recalc
\r
197 el.setWidth(ul.getWidth()+el.getFrameWidth("lr"));
\r
202 delayAutoWidth : function(){
\r
205 this.awTask = new Ext.util.DelayedTask(this.autoWidth, this);
\r
207 this.awTask.delay(20);
\r
212 findTargetItem : function(e){
\r
213 var t = e.getTarget(".x-menu-list-item", this.ul, true);
\r
214 if(t && t.menuItemId){
\r
215 return this.items.get(t.menuItemId);
\r
220 onClick : function(e){
\r
222 if(t = this.findTargetItem(e)){
\r
223 if(t.menu && this.ignoreParentClicks){
\r
227 this.fireEvent("click", this, t, e);
\r
233 setActiveItem : function(item, autoExpand){
\r
234 if(item != this.activeItem){
\r
235 if(this.activeItem){
\r
236 this.activeItem.deactivate();
\r
238 this.activeItem = item;
\r
239 item.activate(autoExpand);
\r
240 }else if(autoExpand){
\r
246 tryActivate : function(start, step){
\r
247 var items = this.items;
\r
248 for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
\r
249 var item = items.get(i);
\r
250 if(!item.disabled && item.canActivate){
\r
251 this.setActiveItem(item, false);
\r
259 onMouseOver : function(e){
\r
261 if(t = this.findTargetItem(e)){
\r
262 if(t.canActivate && !t.disabled){
\r
263 this.setActiveItem(t, true);
\r
267 this.fireEvent("mouseover", this, e, t);
\r
271 onMouseOut : function(e){
\r
273 if(t = this.findTargetItem(e)){
\r
274 if(t == this.activeItem && t.shouldDeactivate(e)){
\r
275 this.activeItem.deactivate();
\r
276 delete this.activeItem;
\r
280 this.fireEvent("mouseout", this, e, t);
\r
284 * Read-only. Returns true if the menu is currently displayed, else false.
\r
287 isVisible : function(){
\r
288 return this.el && !this.hidden;
\r
292 * Displays this menu relative to another element
\r
293 * @param {Mixed} element The element to align to
\r
294 * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to
\r
295 * the element (defaults to this.defaultAlign)
\r
296 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
298 show : function(el, pos, parentMenu){
\r
299 this.parentMenu = parentMenu;
\r
303 this.fireEvent("beforeshow", this);
\r
304 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
\r
308 * Displays this menu at a specific xy position
\r
309 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
\r
310 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
312 showAt : function(xy, parentMenu, /* private: */_e){
\r
313 this.parentMenu = parentMenu;
\r
318 this.fireEvent("beforeshow", this);
\r
319 xy = this.el.adjustForConstraints(xy);
\r
323 this.hidden = false;
\r
325 this.fireEvent("show", this);
\r
330 focus : function(){
\r
332 this.doFocus.defer(50, this);
\r
336 doFocus : function(){
\r
338 this.focusEl.focus();
\r
343 * Hides this menu and optionally all parent menus
\r
344 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
\r
346 hide : function(deep){
\r
347 if(this.el && this.isVisible()){
\r
348 this.fireEvent("beforehide", this);
\r
349 if(this.activeItem){
\r
350 this.activeItem.deactivate();
\r
351 this.activeItem = null;
\r
354 this.hidden = true;
\r
355 this.fireEvent("hide", this);
\r
357 if(deep === true && this.parentMenu){
\r
358 this.parentMenu.hide(true);
\r
363 * Adds one or more items of any type supported by the Menu class, or that can be converted into menu items.
\r
364 * Any of the following are valid:
\r
366 * <li>Any menu item object based on {@link Ext.menu.BaseItem}</li>
\r
367 * <li>An HTMLElement object which will be converted to a menu item</li>
\r
368 * <li>A menu item config object that will be created as a new menu item</li>
\r
369 * <li>A string, which can either be '-' or 'separator' to add a menu separator, otherwise
\r
370 * it will be converted into a {@link Ext.menu.TextItem} and added</li>
\r
375 var menu = new Ext.menu.Menu();
\r
377 // Create a menu item to add by reference
\r
378 var menuItem = new Ext.menu.Item({ text: 'New Item!' });
\r
380 // Add a bunch of items at once using different methods.
\r
381 // Only the last item added will be returned.
\r
382 var item = menu.add(
\r
383 menuItem, // add existing item by ref
\r
384 'Dynamic Item', // new TextItem
\r
385 '-', // new separator
\r
386 { text: 'Config Item' } // new item by config
\r
389 * @param {Mixed} args One or more menu items, menu item configs or other objects that can be converted to menu items
\r
390 * @return {Ext.menu.Item} The menu item that was added, or the last one if multiple items were added
\r
393 var a = arguments, l = a.length, item;
\r
394 for(var i = 0; i < l; i++){
\r
396 if(el.render){ // some kind of Item
\r
397 item = this.addItem(el);
\r
398 }else if(typeof el == "string"){ // string
\r
399 if(el == "separator" || el == "-"){
\r
400 item = this.addSeparator();
\r
402 item = this.addText(el);
\r
404 }else if(el.tagName || el.el){ // element
\r
405 item = this.addElement(el);
\r
406 }else if(typeof el == "object"){ // must be menu item config?
\r
407 Ext.applyIf(el, this.defaults);
\r
408 item = this.addMenuItem(el);
\r
415 * Returns this menu's underlying {@link Ext.Element} object
\r
416 * @return {Ext.Element} The element
\r
418 getEl : function(){
\r
426 * Adds a separator bar to the menu
\r
427 * @return {Ext.menu.Item} The menu item that was added
\r
429 addSeparator : function(){
\r
430 return this.addItem(new Ext.menu.Separator());
\r
434 * Adds an {@link Ext.Element} object to the menu
\r
435 * @param {Mixed} el The element or DOM node to add, or its id
\r
436 * @return {Ext.menu.Item} The menu item that was added
\r
438 addElement : function(el){
\r
439 return this.addItem(new Ext.menu.BaseItem(el));
\r
443 * Adds an existing object based on {@link Ext.menu.BaseItem} to the menu
\r
444 * @param {Ext.menu.Item} item The menu item to add
\r
445 * @return {Ext.menu.Item} The menu item that was added
\r
447 addItem : function(item){
\r
448 this.items.add(item);
\r
450 var li = document.createElement("li");
\r
451 li.className = "x-menu-list-item";
\r
452 this.ul.dom.appendChild(li);
\r
453 item.render(li, this);
\r
454 this.delayAutoWidth();
\r
460 * Creates a new {@link Ext.menu.Item} based an the supplied config object and adds it to the menu
\r
461 * @param {Object} config A MenuItem config object
\r
462 * @return {Ext.menu.Item} The menu item that was added
\r
464 addMenuItem : function(config){
\r
465 if(!(config instanceof Ext.menu.Item)){
\r
466 if(typeof config.checked == "boolean"){ // must be check menu item config?
\r
467 config = new Ext.menu.CheckItem(config);
\r
469 config = new Ext.menu.Item(config);
\r
472 return this.addItem(config);
\r
476 * Creates a new {@link Ext.menu.TextItem} with the supplied text and adds it to the menu
\r
477 * @param {String} text The text to display in the menu item
\r
478 * @return {Ext.menu.Item} The menu item that was added
\r
480 addText : function(text){
\r
481 return this.addItem(new Ext.menu.TextItem(text));
\r
485 * Inserts an existing object based on {@link Ext.menu.BaseItem} to the menu at a specified index
\r
486 * @param {Number} index The index in the menu's list of current items where the new item should be inserted
\r
487 * @param {Ext.menu.Item} item The menu item to add
\r
488 * @return {Ext.menu.Item} The menu item that was added
\r
490 insert : function(index, item){
\r
491 this.items.insert(index, item);
\r
493 var li = document.createElement("li");
\r
494 li.className = "x-menu-list-item";
\r
495 this.ul.dom.insertBefore(li, this.ul.dom.childNodes[index]);
\r
496 item.render(li, this);
\r
497 this.delayAutoWidth();
\r
503 * Removes an {@link Ext.menu.Item} from the menu and destroys the object
\r
504 * @param {Ext.menu.Item} item The menu item to remove
\r
506 remove : function(item){
\r
507 this.items.removeKey(item.id);
\r
512 * Removes and destroys all items in the menu
\r
514 removeAll : function(){
\r
517 while(f = this.items.first()){
\r
524 * Destroys the menu by unregistering it from {@link Ext.menu.MenuMgr}, purging event listeners,
\r
525 * removing all of the menus items, then destroying the underlying {@link Ext.Element}
\r
527 destroy : function(){
\r
528 this.beforeDestroy();
\r
529 Ext.menu.MenuMgr.unregister(this);
\r
531 this.keyNav.disable();
\r
535 this.ul.removeAllListeners();
\r
543 beforeDestroy : Ext.emptyFn
\r
547 // MenuNav is a private utility class used internally by the Menu
\r
548 Ext.menu.MenuNav = function(menu){
\r
549 Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);
\r
550 this.scope = this.menu = menu;
\r
553 Ext.extend(Ext.menu.MenuNav, Ext.KeyNav, {
\r
554 doRelay : function(e, h){
\r
555 var k = e.getKey();
\r
556 if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
\r
557 this.menu.tryActivate(0, 1);
\r
560 return h.call(this.scope || this, e, this.menu);
\r
563 up : function(e, m){
\r
564 if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
\r
565 m.tryActivate(m.items.length-1, -1);
\r
569 down : function(e, m){
\r
570 if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
\r
571 m.tryActivate(0, 1);
\r
575 right : function(e, m){
\r
577 m.activeItem.expandMenu(true);
\r
581 left : function(e, m){
\r
583 if(m.parentMenu && m.parentMenu.activeItem){
\r
584 m.parentMenu.activeItem.activate();
\r
588 enter : function(e, m){
\r
590 e.stopPropagation();
\r
591 m.activeItem.onClick(e);
\r
592 m.fireEvent("click", this, m.activeItem);
\r