4 * Copyright(c) 2006-2007, Ext JS, LLC.
6 * Originally Released Under LGPL - original licence link has changed is not relivant.
9 * <script type="text/javascript">
13 * @class Roo.menu.Menu
14 * @extends Roo.util.Observable
15 * @children Roo.menu.Item Roo.menu.Separator Roo.menu.TextItem
16 * A menu object. This is the container to which you add all other menu items. Menu can also serve a as a base class
17 * when you want a specialzed menu based off of another component (like {@link Roo.menu.DateMenu} for example).
20 * @param {Object} config Configuration options
22 Roo.menu.Menu = function(config){
24 Roo.menu.Menu.superclass.constructor.call(this, config);
26 this.id = this.id || Roo.id();
30 * Fires before this menu is displayed
31 * @param {Roo.menu.Menu} this
36 * Fires before this menu is hidden
37 * @param {Roo.menu.Menu} this
42 * Fires after this menu is displayed
43 * @param {Roo.menu.Menu} this
48 * Fires after this menu is hidden
49 * @param {Roo.menu.Menu} this
54 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
55 * @param {Roo.menu.Menu} this
56 * @param {Roo.menu.Item} menuItem The menu item that was clicked
57 * @param {Roo.EventObject} e
62 * Fires when the mouse is hovering over this menu
63 * @param {Roo.menu.Menu} this
64 * @param {Roo.EventObject} e
65 * @param {Roo.menu.Item} menuItem The menu item that was clicked
70 * Fires when the mouse exits this menu
71 * @param {Roo.menu.Menu} this
72 * @param {Roo.EventObject} e
73 * @param {Roo.menu.Item} menuItem The menu item that was clicked
78 * Fires when a menu item contained in this menu is clicked
79 * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
80 * @param {Roo.EventObject} e
84 if (this.registerMenu) {
85 Roo.menu.MenuMgr.register(this);
89 this.items = new Roo.util.MixedCollection();
91 this.add.apply(this, mis);
95 Roo.extend(Roo.menu.Menu, Roo.util.Observable, {
97 * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
101 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
102 * for bottom-right shadow (defaults to "sides")
106 * @cfg {String} subMenuAlign The {@link Roo.Element#alignTo} anchor position value to use for submenus of
107 * this menu (defaults to "tl-tr?")
109 subMenuAlign : "tl-tr?",
111 * @cfg {String} defaultAlign The default {@link Roo.Element#alignTo) anchor position value for this menu
112 * relative to its element of origin (defaults to "tl-bl?")
114 defaultAlign : "tl-bl?",
116 * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
118 allowOtherMenus : false,
120 * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
131 var el = this.el = new Roo.Layer({
135 parentEl: this.parentEl || document.body,
139 this.keyNav = new Roo.menu.MenuNav(this);
142 el.addClass("x-menu-plain");
145 el.addClass(this.cls);
147 // generic focus element
148 this.focusEl = el.createChild({
149 tag: "a", cls: "x-menu-focus", href: "#", onclick: "return false;", tabIndex:"-1"
151 var ul = el.createChild({tag: "ul", cls: "x-menu-list"});
152 //disabling touch- as it's causing issues ..
153 //ul.on(Roo.isTouch ? 'touchstart' : 'click' , this.onClick, this);
154 ul.on('click' , this.onClick, this);
157 ul.on("mouseover", this.onMouseOver, this);
158 ul.on("mouseout", this.onMouseOut, this);
159 this.items.each(function(item){
164 var li = document.createElement("li");
165 li.className = "x-menu-list-item";
166 ul.dom.appendChild(li);
167 item.render(li, this);
174 autoWidth : function(){
175 var el = this.el, ul = this.ul;
183 el.setWidth(this.minWidth);
184 var t = el.dom.offsetWidth; // force recalc
185 el.setWidth(ul.getWidth()+el.getFrameWidth("lr"));
190 delayAutoWidth : function(){
193 this.awTask = new Roo.util.DelayedTask(this.autoWidth, this);
195 this.awTask.delay(20);
200 findTargetItem : function(e){
201 var t = e.getTarget(".x-menu-list-item", this.ul, true);
202 if(t && t.menuItemId){
203 return this.items.get(t.menuItemId);
208 onClick : function(e){
209 Roo.log("menu.onClick");
210 var t = this.findTargetItem(e);
215 if (Roo.isTouch && e.type == 'touchstart' && t.menu && !t.disabled) {
216 if(t == this.activeItem && t.shouldDeactivate(e)){
217 this.activeItem.deactivate();
218 delete this.activeItem;
222 this.setActiveItem(t, true);
230 this.fireEvent("click", this, t, e);
234 setActiveItem : function(item, autoExpand){
235 if(item != this.activeItem){
237 this.activeItem.deactivate();
239 this.activeItem = item;
240 item.activate(autoExpand);
241 }else if(autoExpand){
247 tryActivate : function(start, step){
248 var items = this.items;
249 for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
250 var item = items.get(i);
251 if(!item.disabled && item.canActivate){
252 this.setActiveItem(item, false);
260 onMouseOver : function(e){
262 if(t = this.findTargetItem(e)){
263 if(t.canActivate && !t.disabled){
264 this.setActiveItem(t, true);
267 this.fireEvent("mouseover", this, e, t);
271 onMouseOut : function(e){
273 if(t = this.findTargetItem(e)){
274 if(t == this.activeItem && t.shouldDeactivate(e)){
275 this.activeItem.deactivate();
276 delete this.activeItem;
279 this.fireEvent("mouseout", this, e, t);
283 * Read-only. Returns true if the menu is currently displayed, else false.
286 isVisible : function(){
287 return this.el && !this.hidden;
291 * Displays this menu relative to another element
292 * @param {String/HTMLElement/Roo.Element} element The element to align to
293 * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
294 * the element (defaults to this.defaultAlign)
295 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
297 show : function(el, pos, parentMenu){
298 this.parentMenu = parentMenu;
302 this.fireEvent("beforeshow", this);
303 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
307 * Displays this menu at a specific xy position
308 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
309 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
311 showAt : function(xy, parentMenu, /* private: */_e){
312 this.parentMenu = parentMenu;
317 this.fireEvent("beforeshow", this);
318 xy = this.el.adjustForConstraints(xy);
324 this.fireEvent("show", this);
329 this.doFocus.defer(50, this);
333 doFocus : function(){
335 this.focusEl.focus();
340 * Hides this menu and optionally all parent menus
341 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
343 hide : function(deep){
344 if(this.el && this.isVisible()){
345 this.fireEvent("beforehide", this);
347 this.activeItem.deactivate();
348 this.activeItem = null;
352 this.fireEvent("hide", this);
354 if(deep === true && this.parentMenu){
355 this.parentMenu.hide(true);
360 * Addds one or more items of any type supported by the Menu class, or that can be converted into menu items.
361 * Any of the following are valid:
363 * <li>Any menu item object based on {@link Roo.menu.Item}</li>
364 * <li>An HTMLElement object which will be converted to a menu item</li>
365 * <li>A menu item config object that will be created as a new menu item</li>
366 * <li>A string, which can either be '-' or 'separator' to add a menu separator, otherwise
367 * it will be converted into a {@link Roo.menu.TextItem} and added</li>
372 var menu = new Roo.menu.Menu();
374 // Create a menu item to add by reference
375 var menuItem = new Roo.menu.Item({ text: 'New Item!' });
377 // Add a bunch of items at once using different methods.
378 // Only the last item added will be returned.
380 menuItem, // add existing item by ref
381 'Dynamic Item', // new TextItem
382 '-', // new separator
383 { text: 'Config Item' } // new item by config
386 * @param {Mixed} args One or more menu items, menu item configs or other objects that can be converted to menu items
387 * @return {Roo.menu.Item} The menu item that was added, or the last one if multiple items were added
390 var a = arguments, l = a.length, item;
391 for(var i = 0; i < l; i++){
393 if ((typeof(el) == "object") && el.xtype && el.xns) {
394 el = Roo.factory(el, Roo.menu);
397 if(el.render){ // some kind of Item
398 item = this.addItem(el);
399 }else if(typeof el == "string"){ // string
400 if(el == "separator" || el == "-"){
401 item = this.addSeparator();
403 item = this.addText(el);
405 }else if(el.tagName || el.el){ // element
406 item = this.addElement(el);
407 }else if(typeof el == "object"){ // must be menu item config?
408 item = this.addMenuItem(el);
415 * Returns this menu's underlying {@link Roo.Element} object
416 * @return {Roo.Element} The element
426 * Adds a separator bar to the menu
427 * @return {Roo.menu.Item} The menu item that was added
429 addSeparator : function(){
430 return this.addItem(new Roo.menu.Separator());
434 * Adds an {@link Roo.Element} object to the menu
435 * @param {String/HTMLElement/Roo.Element} el The element or DOM node to add, or its id
436 * @return {Roo.menu.Item} The menu item that was added
438 addElement : function(el){
439 return this.addItem(new Roo.menu.BaseItem(el));
443 * Adds an existing object based on {@link Roo.menu.Item} to the menu
444 * @param {Roo.menu.Item} item The menu item to add
445 * @return {Roo.menu.Item} The menu item that was added
447 addItem : function(item){
448 this.items.add(item);
450 var li = document.createElement("li");
451 li.className = "x-menu-list-item";
452 this.ul.dom.appendChild(li);
453 item.render(li, this);
454 this.delayAutoWidth();
460 * Creates a new {@link Roo.menu.Item} based an the supplied config object and adds it to the menu
461 * @param {Object} config A MenuItem config object
462 * @return {Roo.menu.Item} The menu item that was added
464 addMenuItem : function(config){
465 if(!(config instanceof Roo.menu.Item)){
466 if(typeof config.checked == "boolean"){ // must be check menu item config?
467 config = new Roo.menu.CheckItem(config);
469 config = new Roo.menu.Item(config);
472 return this.addItem(config);
476 * Creates a new {@link Roo.menu.TextItem} with the supplied text and adds it to the menu
477 * @param {String} text The text to display in the menu item
478 * @return {Roo.menu.Item} The menu item that was added
480 addText : function(text){
481 return this.addItem(new Roo.menu.TextItem({ text : text }));
485 * Inserts an existing object based on {@link Roo.menu.Item} to the menu at a specified index
486 * @param {Number} index The index in the menu's list of current items where the new item should be inserted
487 * @param {Roo.menu.Item} item The menu item to add
488 * @return {Roo.menu.Item} The menu item that was added
490 insert : function(index, item){
491 this.items.insert(index, item);
493 var li = document.createElement("li");
494 li.className = "x-menu-list-item";
495 this.ul.dom.insertBefore(li, this.ul.dom.childNodes[index]);
496 item.render(li, this);
497 this.delayAutoWidth();
503 * Removes an {@link Roo.menu.Item} from the menu and destroys the object
504 * @param {Roo.menu.Item} item The menu item to remove
506 remove : function(item){
507 this.items.removeKey(item.id);
512 * Removes and destroys all items in the menu
514 removeAll : function(){
516 while(f = this.items.first()){
522 // MenuNav is a private utility class used internally by the Menu
523 Roo.menu.MenuNav = function(menu){
524 Roo.menu.MenuNav.superclass.constructor.call(this, menu.el);
525 this.scope = this.menu = menu;
528 Roo.extend(Roo.menu.MenuNav, Roo.KeyNav, {
529 doRelay : function(e, h){
531 if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
532 this.menu.tryActivate(0, 1);
535 return h.call(this.scope || this, e, this.menu);
539 if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
540 m.tryActivate(m.items.length-1, -1);
544 down : function(e, m){
545 if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
550 right : function(e, m){
552 m.activeItem.expandMenu(true);
556 left : function(e, m){
558 if(m.parentMenu && m.parentMenu.activeItem){
559 m.parentMenu.activeItem.activate();
563 enter : function(e, m){
566 m.activeItem.onClick(e);
567 m.fireEvent("click", this, m.activeItem);