Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / src / widgets / menu / Menu.js
index 04b4874..3a348b1 100644 (file)
@@ -1,5 +1,5 @@
 /*!
- * Ext JS Library 3.0.0
+ * Ext JS Library 3.0.3
  * Copyright(c) 2006-2009 Ext JS, LLC
  * licensing@extjs.com
  * http://www.extjs.com/license
  * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>\r
  */\r
  Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {\r
-    monitorResize: true,\r
+    monitorResize : true,\r
 \r
     setContainer : function(ct){\r
         this.monitorResize = !ct.floating;\r
+        // This event is only fired by the menu in IE, used so we don't couple\r
+        // the menu with the layout.\r
+        ct.on('autosize', this.doAutoSize, this);\r
         Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);\r
     },\r
 \r
                 this.itemTpl.append(target, a, true));\r
 \r
 //          Link the containing <li> to the item.\r
-            c.positionEl.menuItemId = c.itemId || c.id;\r
+            c.positionEl.menuItemId = c.getItemId();\r
 \r
 //          If rendering a regular Component, and it needs an icon,\r
 //          move the Component rightwards.\r
             if (!a.isMenuItem && a.needsIcon) {\r
                 c.positionEl.addClass('x-menu-list-item-indent');\r
             }\r
+            this.configureItem(c, position);\r
         }else if(c && !this.isValidParent(c, target)){\r
             if(Ext.isNumber(position)){\r
                 position = target.dom.childNodes[position];\r
@@ -55,7 +59,7 @@
         }\r
     },\r
 \r
-    getItemArgs: function(c) {\r
+    getItemArgs : function(c) {\r
         var isMenuItem = c instanceof Ext.menu.Item;\r
         return {\r
             isMenuItem: isMenuItem,\r
             icon: c.icon || Ext.BLANK_IMAGE_URL,\r
             iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),\r
             itemId: 'x-menu-el-' + c.id,\r
-            itemCls: 'x-menu-list-item ' + (this.extraCls || '')\r
+            itemCls: 'x-menu-list-item '\r
         };\r
     },\r
 \r
 //  Valid if the Component is in a <li> which is part of our target <ul>\r
-    isValidParent: function(c, target) {\r
+    isValidParent : function(c, target) {\r
         return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);\r
     },\r
 \r
@@ -125,20 +129,20 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
      */\r
     minWidth : 120,\r
     /**\r
-     * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"\r
-     * for bottom-right shadow (defaults to "sides")\r
+     * @cfg {Boolean/String} shadow True or 'sides' for the default effect, 'frame' for 4-way shadow, and 'drop'\r
+     * for bottom-right shadow (defaults to 'sides')\r
      */\r
-    shadow : "sides",\r
+    shadow : 'sides',\r
     /**\r
      * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of\r
-     * this menu (defaults to "tl-tr?")\r
+     * this menu (defaults to 'tl-tr?')\r
      */\r
-    subMenuAlign : "tl-tr?",\r
+    subMenuAlign : 'tl-tr?',\r
     /**\r
      * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu\r
-     * relative to its element of origin (defaults to "tl-bl?")\r
+     * relative to its element of origin (defaults to 'tl-bl?')\r
      */\r
-    defaultAlign : "tl-bl?",\r
+    defaultAlign : 'tl-bl?',\r
     /**\r
      * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)\r
      */\r
@@ -151,43 +155,59 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
     /**\r
      * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true).\r
      */\r
-    enableScrolling: true,\r
+    enableScrolling : true,\r
     /**\r
      * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null).\r
      */\r
-    maxHeight: null,\r
+    maxHeight : null,\r
     /**\r
      * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24).\r
      */\r
-    scrollIncrement: 24,\r
+    scrollIncrement : 24,\r
     /**\r
      * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).\r
      */\r
-    showSeparator: true,\r
+    showSeparator : true,\r
     /**\r
      * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to\r
      * change the default Menu popup position after aligning according to the {@link #defaultAlign}\r
      * configuration. Defaults to <tt>[0, 0]</tt>.\r
      */\r
     defaultOffsets : [0, 0],\r
-\r
+    \r
+    /**\r
+     * @cfg {Boolean} plain\r
+     * True to remove the incised line down the left side of the menu. Defaults to <tt>false</tt>.\r
+     */\r
+    plain : false,\r
 \r
     /**\r
      * @cfg {Boolean} floating\r
-     * May be specified as false to create a Menu which may be used as a child item of another Container\r
-     * instead of a free-floating {@link Ext.Layer Layer}. (defaults to true).\r
+     * <p>By default, a Menu configured as <b><code>floating:true</code></b>\r
+     * will be rendered as an {@link Ext.Layer} (an absolutely positioned,\r
+     * floating Component with zindex=15000).\r
+     * If configured as <b><code>floating:false</code></b>, the Menu may be\r
+     * used as child item of another Container instead of a free-floating\r
+     * {@link Ext.Layer Layer}.\r
      */\r
-    floating: true,         // Render as a Layer by default\r
+    floating : true,\r
 \r
     // private\r
-    hidden: true,\r
-    layout: 'menu',\r
-    hideMode: 'offsets',    // Important for laying out Components\r
-    scrollerHeight: 8,\r
-    autoLayout: true,       // Provided for backwards compat\r
-    defaultType: 'menuitem',\r
-\r
-    initComponent: function(){\r
+    hidden : true,\r
+\r
+    /**\r
+     * @cfg {String/Object} layout\r
+     * This class assigns a default layout (<code>layout:'<b>menu</b>'</code>).\r
+     * Developers <i>may</i> override this configuration option if another layout is required.\r
+     * See {@link Ext.Container#layout} for additional information.\r
+     */\r
+    layout : 'menu',\r
+    hideMode : 'offsets',    // Important for laying out Components\r
+    scrollerHeight : 8,\r
+    autoLayout : true,       // Provided for backwards compat\r
+    defaultType : 'menuitem',\r
+\r
+    initComponent : function(){\r
         if(Ext.isArray(this.initialConfig)){\r
             Ext.apply(this, {items:this.initialConfig});\r
         }\r
@@ -300,7 +320,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
 \r
     // private\r
     findTargetItem : function(e){\r
-        var t = e.getTarget(".x-menu-list-item", this.ul, true);\r
+        var t = e.getTarget('.x-menu-list-item', this.ul, true);\r
         if(t && t.menuItemId){\r
             return this.items.get(t.menuItemId);\r
         }\r
@@ -312,13 +332,13 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
         if(t){\r
             if(t.isFormField){\r
                 this.setActiveItem(t);\r
-            }else{\r
+            }else if(t instanceof Ext.menu.BaseItem){\r
                 if(t.menu && this.ignoreParentClicks){\r
                     t.expandMenu();\r
                     e.preventDefault();\r
                 }else if(t.onClick){\r
                     t.onClick(e);\r
-                    this.fireEvent("click", this, t, e);\r
+                    this.fireEvent('click', this, t, e);\r
                 }\r
             }\r
         }\r
@@ -338,7 +358,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
         }\r
     },\r
 \r
-    deactivateActive: function(){\r
+    deactivateActive : function(){\r
         var a = this.activeItem;\r
         if(a){\r
             if(a.isFormField){\r
@@ -375,7 +395,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
             }\r
         }\r
         this.over = true;\r
-        this.fireEvent("mouseover", this, e, t);\r
+        this.fireEvent('mouseover', this, e, t);\r
     },\r
 \r
     // private\r
@@ -388,11 +408,11 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
             }\r
         }\r
         this.over = false;\r
-        this.fireEvent("mouseout", this, e, t);\r
+        this.fireEvent('mouseout', this, e, t);\r
     },\r
 \r
     // private\r
-    onScroll: function(e, t){\r
+    onScroll : function(e, t){\r
         if(e){\r
             e.stopEvent();\r
         }\r
@@ -404,7 +424,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
     },\r
 \r
     // private\r
-    onScrollerIn: function(e, t){\r
+    onScrollerIn : function(e, t){\r
         var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');\r
         if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){\r
             Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);\r
@@ -412,12 +432,13 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
     },\r
 \r
     // private\r
-    onScrollerOut: function(e, t){\r
+    onScrollerOut : function(e, t){\r
         Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);\r
     },\r
 \r
     /**\r
-     * Displays this menu relative to another element\r
+     * If <code>{@link #floating}=true</code>, shows this menu relative to\r
+     * another element using {@link #showat}, otherwise uses {@link Ext.Component#show}.\r
      * @param {Mixed} element The element to align to\r
      * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to\r
      * the element (defaults to this.defaultAlign)\r
@@ -430,42 +451,51 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
                 this.render();\r
                 this.doLayout(false, true);\r
             }\r
-            if(this.fireEvent('beforeshow', this) !== false){\r
-                this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu, false);\r
-            }\r
+            this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu);\r
         }else{\r
             Ext.menu.Menu.superclass.show.call(this);\r
         }\r
     },\r
 \r
     /**\r
-     * Displays this menu at a specific xy position\r
+     * Displays this menu at a specific xy position and fires the 'show' event if a\r
+     * handler for the 'beforeshow' event does not return false cancelling the operation.\r
      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)\r
      * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)\r
      */\r
-    showAt : function(xy, parentMenu, /* private: */_e){\r
-        this.parentMenu = parentMenu;\r
-        if(!this.el){\r
-            this.render();\r
-        }\r
-        this.el.setXY(xy);\r
-        if(this.enableScrolling){\r
-            this.constrainScroll(xy[1]);\r
-        }\r
-        this.el.show();\r
-        Ext.menu.Menu.superclass.onShow.call(this);\r
-        if(Ext.isIE){\r
-            this.layout.doAutoSize();\r
-            if(!Ext.isIE8){\r
-                this.el.repaint();\r
+    showAt : function(xy, parentMenu){\r
+        if(this.fireEvent('beforeshow', this) !== false){\r
+            this.parentMenu = parentMenu;\r
+            if(!this.el){\r
+                this.render();\r
+            }\r
+            if(this.enableScrolling){\r
+                // set the position so we can figure out the constrain value.\r
+                this.el.setXY(xy);\r
+                //constrain the value, keep the y coordinate the same\r
+                this.constrainScroll(xy[1]);\r
+                xy = [this.el.adjustForConstraints(xy)[0], xy[1]];\r
+            }else{\r
+                //constrain to the viewport.\r
+                xy = this.el.adjustForConstraints(xy);\r
             }\r
+            this.el.setXY(xy);\r
+            this.el.show();\r
+            Ext.menu.Menu.superclass.onShow.call(this);\r
+            if(Ext.isIE){\r
+                // internal event, used so we don't couple the layout to the menu\r
+                this.fireEvent('autosize', this);\r
+                if(!Ext.isIE8){\r
+                    this.el.repaint();\r
+                }\r
+            }\r
+            this.hidden = false;\r
+            this.focus();\r
+            this.fireEvent('show', this);\r
         }\r
-        this.hidden = false;\r
-        this.focus();\r
-        this.fireEvent("show", this);\r
     },\r
 \r
-    constrainScroll: function(y){\r
+    constrainScroll : function(y){\r
         var max, full = this.ul.setHeight('auto').getHeight();\r
         if(this.floating){\r
             max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize().height - y;\r
@@ -484,7 +514,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
         this.ul.dom.scrollTop = 0;\r
     },\r
 \r
-    createScrollers: function(){\r
+    createScrollers : function(){\r
         if(!this.scroller){\r
             this.scroller = {\r
                 pos: 0,\r
@@ -514,7 +544,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
         }\r
     },\r
 \r
-    onLayout: function(){\r
+    onLayout : function(){\r
         if(this.isVisible()){\r
             if(this.enableScrolling){\r
                 this.constrainScroll(this.el.getTop());\r
@@ -548,19 +578,24 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
     },\r
 \r
     // private\r
-    onHide: function(){\r
+    onHide : function(){\r
         Ext.menu.Menu.superclass.onHide.call(this);\r
         this.deactivateActive();\r
         if(this.el && this.floating){\r
             this.el.hide();\r
         }\r
-        if(this.deepHide === true && this.parentMenu){\r
-            this.parentMenu.hide(true);\r
+        var pm = this.parentMenu;\r
+        if(this.deepHide === true && pm){\r
+            if(pm.floating){\r
+                pm.hide(true);\r
+            }else{\r
+                pm.deactivateActive();\r
+            }\r
         }\r
     },\r
 \r
     // private\r
-    lookupComponent: function(c){\r
+    lookupComponent : function(c){\r
          if(Ext.isString(c)){\r
             c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);\r
              this.applyDefaults(c);\r
@@ -593,7 +628,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
     },\r
 \r
     // private\r
-    getMenuItem: function(config){\r
+    getMenuItem : function(config){\r
        if(!config.isXType){\r
             if(!config.xtype && Ext.isBoolean(config.checked)){\r
                 return new Ext.menu.CheckItem(config)\r
@@ -659,6 +694,11 @@ Ext.menu.Menu = Ext.extend(Ext.Container, {
         if(s){\r
             Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);\r
         }\r
+        Ext.destroy(\r
+            this.el,\r
+            this.focusEl,\r
+            this.ul\r
+        );\r
     }\r
 });\r
 \r
@@ -677,7 +717,7 @@ Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
         }\r
     }\r
     return {\r
-        constructor: function(menu){\r
+        constructor : function(menu){\r
             Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);\r
             this.scope = this.menu = menu;\r
         },\r
@@ -725,9 +765,9 @@ Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
             if(m.activeItem){\r
                 e.stopPropagation();\r
                 m.activeItem.onClick(e);\r
-                m.fireEvent("click", this, m.activeItem);\r
+                m.fireEvent('click', this, m.activeItem);\r
                 return true;\r
             }\r
         }\r
     };\r
-}());
\ No newline at end of file
+}());\r