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 * A menu object. This is the container to which you add all other menu items. Menu can also serve a as a base class
16 * when you want a specialzed menu based off of another component (like {@link Roo.menu.DateMenu} for example).
19 * @param {Object} config Configuration options
21 Roo.menu.Menu = function(config){
23 Roo.menu.Menu.superclass.constructor.call(this, config);
25 this.id = this.id || Roo.id();
29 * Fires before this menu is displayed
30 * @param {Roo.menu.Menu} this
35 * Fires before this menu is hidden
36 * @param {Roo.menu.Menu} this
41 * Fires after this menu is displayed
42 * @param {Roo.menu.Menu} this
47 * Fires after this menu is hidden
48 * @param {Roo.menu.Menu} this
53 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
54 * @param {Roo.menu.Menu} this
55 * @param {Roo.menu.Item} menuItem The menu item that was clicked
56 * @param {Roo.EventObject} e
61 * Fires when the mouse is hovering over this menu
62 * @param {Roo.menu.Menu} this
63 * @param {Roo.EventObject} e
64 * @param {Roo.menu.Item} menuItem The menu item that was clicked
69 * Fires when the mouse exits this menu
70 * @param {Roo.menu.Menu} this
71 * @param {Roo.EventObject} e
72 * @param {Roo.menu.Item} menuItem The menu item that was clicked
77 * Fires when a menu item contained in this menu is clicked
78 * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
79 * @param {Roo.EventObject} e
83 if (this.registerMenu) {
84 Roo.menu.MenuMgr.register(this);
88 this.items = new Roo.util.MixedCollection();
90 this.add.apply(this, mis);
94 Roo.extend(Roo.menu.Menu, Roo.util.Observable, {
96 * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
100 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
101 * for bottom-right shadow (defaults to "sides")
105 * @cfg {String} subMenuAlign The {@link Roo.Element#alignTo} anchor position value to use for submenus of
106 * this menu (defaults to "tl-tr?")
108 subMenuAlign : "tl-tr?",
110 * @cfg {String} defaultAlign The default {@link Roo.Element#alignTo) anchor position value for this menu
111 * relative to its element of origin (defaults to "tl-bl?")
113 defaultAlign : "tl-bl?",
115 * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
117 allowOtherMenus : false,
119 * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
130 var el = this.el = new Roo.Layer({
134 parentEl: this.parentEl || document.body,
138 this.keyNav = new Roo.menu.MenuNav(this);
141 el.addClass("x-menu-plain");
144 el.addClass(this.cls);
146 // generic focus element
147 this.focusEl = el.createChild({
148 tag: "a", cls: "x-menu-focus", href: "#", onclick: "return false;", tabIndex:"-1"
150 var ul = el.createChild({tag: "ul", cls: "x-menu-list"});
151 //disabling touch- as it's causing issues ..
152 //ul.on(Roo.isTouch ? 'touchstart' : 'click' , this.onClick, this);
153 ul.on('click' , this.onClick, this);
156 ul.on("mouseover", this.onMouseOver, this);
157 ul.on("mouseout", this.onMouseOut, this);
158 this.items.each(function(item){
163 var li = document.createElement("li");
164 li.className = "x-menu-list-item";
165 ul.dom.appendChild(li);
166 item.render(li, this);
173 autoWidth : function(){
174 var el = this.el, ul = this.ul;
182 el.setWidth(this.minWidth);
183 var t = el.dom.offsetWidth; // force recalc
184 el.setWidth(ul.getWidth()+el.getFrameWidth("lr"));
189 delayAutoWidth : function(){
192 this.awTask = new Roo.util.DelayedTask(this.autoWidth, this);
194 this.awTask.delay(20);
199 findTargetItem : function(e){
200 var t = e.getTarget(".x-menu-list-item", this.ul, true);
201 if(t && t.menuItemId){
202 return this.items.get(t.menuItemId);
207 onClick : function(e){
208 Roo.log("menu.onClick");
209 var t = this.findTargetItem(e);
214 if (Roo.isTouch && e.type == 'touchstart' && t.menu && !t.disabled) {
215 if(t == this.activeItem && t.shouldDeactivate(e)){
216 this.activeItem.deactivate();
217 delete this.activeItem;
221 this.setActiveItem(t, true);
229 this.fireEvent("click", this, t, e);
233 setActiveItem : function(item, autoExpand){
234 if(item != this.activeItem){
236 this.activeItem.deactivate();
238 this.activeItem = item;
239 item.activate(autoExpand);
240 }else if(autoExpand){
246 tryActivate : function(start, step){
247 var items = this.items;
248 for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
249 var item = items.get(i);
250 if(!item.disabled && item.canActivate){
251 this.setActiveItem(item, false);
259 onMouseOver : function(e){
261 if(t = this.findTargetItem(e)){
262 if(t.canActivate && !t.disabled){
263 this.setActiveItem(t, true);
266 this.fireEvent("mouseover", this, e, t);
270 onMouseOut : function(e){
272 if(t = this.findTargetItem(e)){
273 if(t == this.activeItem && t.shouldDeactivate(e)){
274 this.activeItem.deactivate();
275 delete this.activeItem;
278 this.fireEvent("mouseout", this, e, t);
282 * Read-only. Returns true if the menu is currently displayed, else false.
285 isVisible : function(){
286 return this.el && !this.hidden;
290 * Displays this menu relative to another element
291 * @param {String/HTMLElement/Roo.Element} element The element to align to
292 * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
293 * the element (defaults to this.defaultAlign)
294 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
296 show : function(el, pos, parentMenu){
297 this.parentMenu = parentMenu;
301 this.fireEvent("beforeshow", this);
302 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
306 * Displays this menu at a specific xy position
307 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
308 * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
310 showAt : function(xy, parentMenu, /* private: */_e){
311 this.parentMenu = parentMenu;
316 this.fireEvent("beforeshow", this);
317 xy = this.el.adjustForConstraints(xy);
323 this.fireEvent("show", this);
328 this.doFocus.defer(50, this);
332 doFocus : function(){
334 this.focusEl.focus();
339 * Hides this menu and optionally all parent menus
340 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
342 hide : function(deep){
343 if(this.el && this.isVisible()){
344 this.fireEvent("beforehide", this);
346 this.activeItem.deactivate();
347 this.activeItem = null;
351 this.fireEvent("hide", this);
353 if(deep === true && this.parentMenu){
354 this.parentMenu.hide(true);
359 * Addds one or more items of any type supported by the Menu class, or that can be converted into menu items.
360 * Any of the following are valid:
362 * <li>Any menu item object based on {@link Roo.menu.Item}</li>
363 * <li>An HTMLElement object which will be converted to a menu item</li>
364 * <li>A menu item config object that will be created as a new menu item</li>
365 * <li>A string, which can either be '-' or 'separator' to add a menu separator, otherwise
366 * it will be converted into a {@link Roo.menu.TextItem} and added</li>
371 var menu = new Roo.menu.Menu();
373 // Create a menu item to add by reference
374 var menuItem = new Roo.menu.Item({ text: 'New Item!' });
376 // Add a bunch of items at once using different methods.
377 // Only the last item added will be returned.
379 menuItem, // add existing item by ref
380 'Dynamic Item', // new TextItem
381 '-', // new separator
382 { text: 'Config Item' } // new item by config
385 * @param {Mixed} args One or more menu items, menu item configs or other objects that can be converted to menu items
386 * @return {Roo.menu.Item} The menu item that was added, or the last one if multiple items were added
389 var a = arguments, l = a.length, item;
390 for(var i = 0; i < l; i++){
392 if ((typeof(el) == "object") && el.xtype && el.xns) {
393 el = Roo.factory(el, Roo.menu);
396 if(el.render){ // some kind of Item
397 item = this.addItem(el);
398 }else if(typeof el == "string"){ // string
399 if(el == "separator" || el == "-"){
400 item = this.addSeparator();
402 item = this.addText(el);
404 }else if(el.tagName || el.el){ // element
405 item = this.addElement(el);
406 }else if(typeof el == "object"){ // must be menu item config?
407 item = this.addMenuItem(el);
414 * Returns this menu's underlying {@link Roo.Element} object
415 * @return {Roo.Element} The element
425 * Adds a separator bar to the menu
426 * @return {Roo.menu.Item} The menu item that was added
428 addSeparator : function(){
429 return this.addItem(new Roo.menu.Separator());
433 * Adds an {@link Roo.Element} object to the menu
434 * @param {String/HTMLElement/Roo.Element} el The element or DOM node to add, or its id
435 * @return {Roo.menu.Item} The menu item that was added
437 addElement : function(el){
438 return this.addItem(new Roo.menu.BaseItem(el));
442 * Adds an existing object based on {@link Roo.menu.Item} to the menu
443 * @param {Roo.menu.Item} item The menu item to add
444 * @return {Roo.menu.Item} The menu item that was added
446 addItem : function(item){
447 this.items.add(item);
449 var li = document.createElement("li");
450 li.className = "x-menu-list-item";
451 this.ul.dom.appendChild(li);
452 item.render(li, this);
453 this.delayAutoWidth();
459 * Creates a new {@link Roo.menu.Item} based an the supplied config object and adds it to the menu
460 * @param {Object} config A MenuItem config object
461 * @return {Roo.menu.Item} The menu item that was added
463 addMenuItem : function(config){
464 if(!(config instanceof Roo.menu.Item)){
465 if(typeof config.checked == "boolean"){ // must be check menu item config?
466 config = new Roo.menu.CheckItem(config);
468 config = new Roo.menu.Item(config);
471 return this.addItem(config);
475 * Creates a new {@link Roo.menu.TextItem} with the supplied text and adds it to the menu
476 * @param {String} text The text to display in the menu item
477 * @return {Roo.menu.Item} The menu item that was added
479 addText : function(text){
480 return this.addItem(new Roo.menu.TextItem({ text : text }));
484 * Inserts an existing object based on {@link Roo.menu.Item} to the menu at a specified index
485 * @param {Number} index The index in the menu's list of current items where the new item should be inserted
486 * @param {Roo.menu.Item} item The menu item to add
487 * @return {Roo.menu.Item} The menu item that was added
489 insert : function(index, item){
490 this.items.insert(index, item);
492 var li = document.createElement("li");
493 li.className = "x-menu-list-item";
494 this.ul.dom.insertBefore(li, this.ul.dom.childNodes[index]);
495 item.render(li, this);
496 this.delayAutoWidth();
502 * Removes an {@link Roo.menu.Item} from the menu and destroys the object
503 * @param {Roo.menu.Item} item The menu item to remove
505 remove : function(item){
506 this.items.removeKey(item.id);
511 * Removes and destroys all items in the menu
513 removeAll : function(){
515 while(f = this.items.first()){
521 // MenuNav is a private utility class used internally by the Menu
522 Roo.menu.MenuNav = function(menu){
523 Roo.menu.MenuNav.superclass.constructor.call(this, menu.el);
524 this.scope = this.menu = menu;
527 Roo.extend(Roo.menu.MenuNav, Roo.KeyNav, {
528 doRelay : function(e, h){
530 if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
531 this.menu.tryActivate(0, 1);
534 return h.call(this.scope || this, e, this.menu);
538 if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
539 m.tryActivate(m.items.length-1, -1);
543 down : function(e, m){
544 if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
549 right : function(e, m){
551 m.activeItem.expandMenu(true);
555 left : function(e, m){
557 if(m.parentMenu && m.parentMenu.activeItem){
558 m.parentMenu.activeItem.activate();
562 enter : function(e, m){
565 m.activeItem.onClick(e);
566 m.fireEvent("click", this, m.activeItem);