| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 | <!DOCTYPE html><html><head>  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  <title>The source code</title>  <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />  <script type="text/javascript" src="../resources/prettify/prettify.js"></script>  <style type="text/css">    .highlight { display: block; background-color: #ddd; }  </style>  <script type="text/javascript">    function highlight() {      document.getElementById(location.hash.replace(/#/, "")).className = "highlight";    }  </script></head><body onload="prettyPrint(); highlight();">  <pre class="prettyprint lang-js"><span id='Ext-menu-Menu'>/**</span> * A menu object. This is the container to which you may add {@link Ext.menu.Item menu items}. * * Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Components}. * Menus may also contain {@link Ext.panel.AbstractPanel#dockedItems docked items} because it extends {@link Ext.panel.Panel}. * * To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items}, * specify `{@link Ext.menu.Item#plain plain}: true`. This reserves a space for an icon, and indents the Component * in line with the other menu items. * * By default, Menus are absolutely positioned, floating Components. By configuring a Menu with `{@link #floating}: false`, * a Menu may be used as a child of a {@link Ext.container.Container Container}. * *     @example *     Ext.create('Ext.menu.Menu', { *         width: 100, *         margin: '0 0 10 0', *         floating: false,  // usually you want this set to True (default) *         renderTo: Ext.getBody(),  // usually rendered by it's containing component *         items: [{ *             text: 'regular item 1' *         },{ *             text: 'regular item 2' *         },{ *             text: 'regular item 3' *         }] *     }); * *     Ext.create('Ext.menu.Menu', { *         width: 100, *         plain: true, *         floating: false,  // usually you want this set to True (default) *         renderTo: Ext.getBody(),  // usually rendered by it's containing component *         items: [{ *             text: 'plain item 1' *         },{ *             text: 'plain item 2' *         },{ *             text: 'plain item 3' *         }] *     }); */Ext.define('Ext.menu.Menu', {    extend: 'Ext.panel.Panel',    alias: 'widget.menu',    requires: [        'Ext.layout.container.Fit',        'Ext.layout.container.VBox',        'Ext.menu.CheckItem',        'Ext.menu.Item',        'Ext.menu.KeyNav',        'Ext.menu.Manager',        'Ext.menu.Separator'    ],<span id='Ext-menu-Menu-property-parentMenu'>    /**</span>     * @property {Ext.menu.Menu} parentMenu     * The parent Menu of this Menu.     */    <span id='Ext-menu-Menu-cfg-enableKeyNav'>    /**</span>     * @cfg {Boolean} [enableKeyNav=true]     * True to enable keyboard navigation for controlling the menu.     * This option should generally be disabled when form fields are     * being used inside the menu.     */    enableKeyNav: true,<span id='Ext-menu-Menu-cfg-allowOtherMenus'>    /**</span>     * @cfg {Boolean} [allowOtherMenus=false]     * True to allow multiple menus to be displayed at the same time.     */    allowOtherMenus: false,<span id='Ext-menu-Menu-cfg-ariaRole'>    /**</span>     * @cfg {String} ariaRole     * @private     */    ariaRole: 'menu',<span id='Ext-menu-Menu-cfg-autoRender'>    /**</span>     * @cfg {Boolean} autoRender     * Floating is true, so autoRender always happens.     * @private     */<span id='Ext-menu-Menu-cfg-defaultAlign'>    /**</span>     * @cfg {String} [defaultAlign="tl-bl?"]     * The default {@link Ext.Element#getAlignToXY Ext.Element#getAlignToXY} anchor position value for this menu     * relative to its element of origin.     */    defaultAlign: 'tl-bl?',<span id='Ext-menu-Menu-cfg-floating'>    /**</span>     * @cfg {Boolean} [floating=true]     * A Menu configured as `floating: true` (the default) will be rendered as an absolutely positioned,     * {@link Ext.Component#floating floating} {@link Ext.Component Component}. If configured as `floating: false`, the Menu may be     * used as a child item of another {@link Ext.container.Container Container}.     */    floating: true,<span id='Ext-menu-Menu-cfg-constrain'>    /**</span>     * @cfg {Boolean} constrain     * Menus are constrained to the document body by default.     * @private     */    constrain: true,<span id='Ext-menu-Menu-cfg-hidden'>    /**</span>     * @cfg {Boolean} [hidden=undefined]     * True to initially render the Menu as hidden, requiring to be shown manually.     *     * Defaults to `true` when `floating: true`, and defaults to `false` when `floating: false`.     */    hidden: true,    hideMode: 'visibility',<span id='Ext-menu-Menu-cfg-ignoreParentClicks'>    /**</span>     * @cfg {Boolean} [ignoreParentClicks=false]     * True to ignore clicks on any item in this menu that is a parent item (displays a submenu)     * so that the submenu is not dismissed when clicking the parent item.     */    ignoreParentClicks: false,<span id='Ext-menu-Menu-property-isMenu'>    /**</span>     * @property {Boolean} isMenu     * `true` in this class to identify an object as an instantiated Menu, or subclass thereof.     */    isMenu: true,<span id='Ext-menu-Menu-cfg-layout'>    /**</span>     * @cfg {String/Object} layout     * @private     */<span id='Ext-menu-Menu-cfg-showSeparator'>    /**</span>     * @cfg {Boolean} [showSeparator=true]     * True to show the icon separator.     */    showSeparator : true,<span id='Ext-menu-Menu-cfg-minWidth'>    /**</span>     * @cfg {Number} [minWidth=120]     * The minimum width of the Menu. The default minWidth only applies when the {@link #floating} config is true.     */    minWidth: undefined,        defaultMinWidth: 120,<span id='Ext-menu-Menu-cfg-plain'>    /**</span>     * @cfg {Boolean} [plain=false]     * True to remove the incised line down the left side of the menu and to not indent general Component items.     */    initComponent: function() {        var me = this,            prefix = Ext.baseCSSPrefix,            cls = [prefix + 'menu'],            bodyCls = me.bodyCls ? [me.bodyCls] : [],            isFloating = me.floating !== false;        me.addEvents(<span id='Ext-menu-Menu-event-click'>            /**</span>             * @event click             * Fires when this menu is clicked             * @param {Ext.menu.Menu} menu The menu which has been clicked             * @param {Ext.Component} item The menu item that was clicked. `undefined` if not applicable.             * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}.             */            'click',<span id='Ext-menu-Menu-event-mouseenter'>            /**</span>             * @event mouseenter             * Fires when the mouse enters this menu             * @param {Ext.menu.Menu} menu The menu             * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}             */            'mouseenter',<span id='Ext-menu-Menu-event-mouseleave'>            /**</span>             * @event mouseleave             * Fires when the mouse leaves this menu             * @param {Ext.menu.Menu} menu The menu             * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}             */            'mouseleave',<span id='Ext-menu-Menu-event-mouseover'>            /**</span>             * @event mouseover             * Fires when the mouse is hovering over this menu             * @param {Ext.menu.Menu} menu The menu             * @param {Ext.Component} item The menu item that the mouse is over. `undefined` if not applicable.             * @param {Ext.EventObject} e The underlying {@link Ext.EventObject}             */            'mouseover'        );        Ext.menu.Manager.register(me);        // Menu classes        if (me.plain) {            cls.push(prefix + 'menu-plain');        }        me.cls = cls.join(' ');        // Menu body classes        bodyCls.unshift(prefix + 'menu-body');        me.bodyCls = bodyCls.join(' ');        // Internal vbox layout, with scrolling overflow        // Placed in initComponent (rather than prototype) in order to support dynamic layout/scroller        // options if we wish to allow for such configurations on the Menu.        // e.g., scrolling speed, vbox align stretch, etc.        if (!me.layout) {            me.layout = {                type: 'vbox',                align: 'stretchmax',                overflowHandler: 'Scroller'            };        }                // only apply the minWidth when we're floating & one hasn't already been set        if (isFloating && me.minWidth === undefined) {            me.minWidth = me.defaultMinWidth;        }        // hidden defaults to false if floating is configured as false        if (!isFloating && me.initialConfig.hidden !== true) {            me.hidden = false;        }        me.callParent(arguments);        me.on('beforeshow', function() {            var hasItems = !!me.items.length;            // FIXME: When a menu has its show cancelled because of no items, it            // gets a visibility: hidden applied to it (instead of the default display: none)            // Not sure why, but we remove this style when we want to show again.            if (hasItems && me.rendered) {                me.el.setStyle('visibility', null);            }            return hasItems;        });    },    beforeRender: function() {        this.callParent(arguments);        // Menus are usually floating: true, which means they shrink wrap their items.        // However, when they are contained, and not auto sized, we must stretch the items.        if (!this.getSizeModel().width.shrinkWrap) {            this.layout.align = 'stretch';        }    },    onBoxReady: function() {        var me = this,            separatorSpec;        me.callParent(arguments);        // TODO: Move this to a subTemplate When we support them in the future        if (me.showSeparator) {            separatorSpec = {                cls: Ext.baseCSSPrefix + 'menu-icon-separator',                html: '&#160;'            };            if ((!Ext.isStrict && Ext.isIE) || Ext.isIE6) {                separatorSpec.style = 'height:' + me.el.getHeight() + 'px';            }            me.iconSepEl = me.layout.getElementTarget().insertFirst(separatorSpec);        }        me.mon(me.el, {            click: me.onClick,            mouseover: me.onMouseOver,            scope: me        });        me.mouseMonitor = me.el.monitorMouseLeave(100, me.onMouseLeave, me);        if (me.enableKeyNav) {            me.keyNav = new Ext.menu.KeyNav(me);        }    },    getBubbleTarget: function() {        // If a submenu, this will have a parentMenu property        // If a menu of a Button, it will have an ownerButton property        // Else use the default method.        return this.parentMenu || this.ownerButton || this.callParent(arguments);    },<span id='Ext-menu-Menu-method-canActivateItem'>    /**</span>     * Returns whether a menu item can be activated or not.     * @return {Boolean}     */    canActivateItem: function(item) {        return item && !item.isDisabled() && item.isVisible() && (item.canActivate || item.getXTypes().indexOf('menuitem') < 0);    },<span id='Ext-menu-Menu-method-deactivateActiveItem'>    /**</span>     * Deactivates the current active item on the menu, if one exists.     */    deactivateActiveItem: function(andBlurFocusedItem) {        var me = this,            activeItem = me.activeItem,            focusedItem = me.focusedItem;        if (activeItem) {            activeItem.deactivate();            if (!activeItem.activated) {                delete me.activeItem;            }        }        // Blur the focused item if we are being asked to do that too        // Only needed if we are being hidden - mouseout does not blur.        if (focusedItem && andBlurFocusedItem) {            focusedItem.blur();            delete me.focusedItem;        }    },    // inherit docs    getFocusEl: function() {        return this.focusedItem || this.el;    },    // inherit docs    hide: function() {        this.deactivateActiveItem(true);        this.callParent(arguments);    },    // private    getItemFromEvent: function(e) {        return this.getChildByElement(e.getTarget());    },    lookupComponent: function(cmp) {        var me = this;        if (typeof cmp == 'string') {            cmp = me.lookupItemFromString(cmp);        } else if (Ext.isObject(cmp)) {            cmp = me.lookupItemFromObject(cmp);        }        // Apply our minWidth to all of our child components so it's accounted        // for in our VBox layout        cmp.minWidth = cmp.minWidth || me.minWidth;        return cmp;    },    // private    lookupItemFromObject: function(cmp) {        var me = this,            prefix = Ext.baseCSSPrefix,            cls;        if (!cmp.isComponent) {            if (!cmp.xtype) {                cmp = Ext.create('Ext.menu.' + (Ext.isBoolean(cmp.checked) ? 'Check': '') + 'Item', cmp);            } else {                cmp = Ext.ComponentManager.create(cmp, cmp.xtype);            }        }        if (cmp.isMenuItem) {            cmp.parentMenu = me;        }        if (!cmp.isMenuItem && !cmp.dock) {            cls = [prefix + 'menu-item', prefix + 'menu-item-cmp'];            if (!me.plain && (cmp.indent === true || cmp.iconCls === 'no-icon')) {                cls.push(prefix + 'menu-item-indent');            }            if (cmp.rendered) {                cmp.el.addCls(cls);            } else {                cmp.cls = (cmp.cls ? cmp.cls : '') + ' ' + cls.join(' ');            }        }        return cmp;    },    // private    lookupItemFromString: function(cmp) {        return (cmp == 'separator' || cmp == '-') ?            new Ext.menu.Separator()            : new Ext.menu.Item({                canActivate: false,                hideOnClick: false,                plain: true,                text: cmp            });    },    onClick: function(e) {        var me = this,            item;        if (me.disabled) {            e.stopEvent();            return;        }        item = (e.type === 'click') ? me.getItemFromEvent(e) : me.activeItem;        if (item && item.isMenuItem) {            if (!item.menu || !me.ignoreParentClicks) {                item.onClick(e);            } else {                e.stopEvent();            }        }        // Click event may be fired without an item, so we need a second check        if (!item || item.disabled) {            item = undefined;        }        me.fireEvent('click', me, item, e);    },    onDestroy: function() {        var me = this;        Ext.menu.Manager.unregister(me);        delete me.parentMenu;        delete me.ownerButton;        if (me.rendered) {            me.el.un(me.mouseMonitor);            Ext.destroy(me.keyNav);            delete me.keyNav;        }        me.callParent(arguments);    },    onMouseLeave: function(e) {        var me = this;        me.deactivateActiveItem();        if (me.disabled) {            return;        }        me.fireEvent('mouseleave', me, e);    },    onMouseOver: function(e) {        var me = this,            fromEl = e.getRelatedTarget(),            mouseEnter = !me.el.contains(fromEl),            item = me.getItemFromEvent(e),            parentMenu = me.parentMenu,            parentItem = me.parentItem;        if (mouseEnter && parentMenu) {            parentMenu.setActiveItem(parentItem);            parentItem.cancelDeferHide();            parentMenu.mouseMonitor.mouseenter();        }        if (me.disabled) {            return;        }        // Do not activate the item if the mouseover was within the item, and it's already active        if (item && !item.activated) {            me.setActiveItem(item);            if (item.activated && item.expandMenu) {                item.expandMenu();            }        }        if (mouseEnter) {            me.fireEvent('mouseenter', me, e);        }        me.fireEvent('mouseover', me, item, e);    },    setActiveItem: function(item) {        var me = this;        if (item && (item != me.activeItem)) {            me.deactivateActiveItem();            if (me.canActivateItem(item)) {                if (item.activate) {                    item.activate();                    if (item.activated) {                        me.activeItem = item;                        me.focusedItem = item;                        me.focus();                    }                } else {                    item.focus();                    me.focusedItem = item;                }            }            item.el.scrollIntoView(me.layout.getRenderTarget());        }    },<span id='Ext-menu-Menu-method-showBy'>    /**</span>     * Shows the floating menu by the specified {@link Ext.Component Component} or {@link Ext.Element Element}.     * @param {Ext.Component/Ext.Element} component The {@link Ext.Component} or {@link Ext.Element} to show the menu by.     * @param {String} [position] Alignment position as used by {@link Ext.Element#getAlignToXY}.     * Defaults to `{@link #defaultAlign}`.     * @param {Number[]} [offsets] Alignment offsets as used by {@link Ext.Element#getAlignToXY}.     * @return {Ext.menu.Menu} This Menu.     */    showBy: function(cmp, pos, off) {        var me = this;        if (me.floating && cmp) {            me.show();            // Align to Component or Element using setPagePosition because normal show            // methods are container-relative, and we must align to the requested element            // or Component:            me.setPagePosition(me.el.getAlignToXY(cmp.el || cmp, pos || me.defaultAlign, off));            me.setVerticalPosition();        }        return me;    },    show: function() {        var me = this,            parentEl, viewHeight, result,            maxWas = me.maxHeight;        // we need to get scope parent for height constraint        if (!me.rendered){            me.doAutoRender();        }        // constrain the height to the curren viewable area        if (me.floating) {            //if our reset css is scoped, there will be a x-reset wrapper on this menu which we need to skip            parentEl = Ext.fly(me.el.getScopeParent());            viewHeight = parentEl.getViewSize().height;            me.maxHeight  =  Math.min(maxWas || viewHeight, viewHeight);        }        result = me.callParent(arguments);        me.maxHeight = maxWas;        return result;    },    afterComponentLayout: function(width, height, oldWidth, oldHeight){        var me = this;        me.callParent(arguments);        // fixup the separator        if (me.showSeparator){            me.iconSepEl.setHeight(me.componentLayout.lastComponentSize.contentHeight);        }    },    // private    // adjust the vertical position of the menu if the height of the    // menu is equal (or greater than) the viewport size    setVerticalPosition: function(){        var me = this,            max,            y = me.el.getY(),            returnY = y,            height = me.getHeight(),            viewportHeight = Ext.Element.getViewportHeight().height,            parentEl = Ext.fly(me.el.getScopeParent()),            viewHeight = parentEl.getViewSize().height,            normalY = y - parentEl.getScroll().top; // factor in scrollTop of parent        parentEl = null;        if (me.floating) {            max = me.maxHeight ? me.maxHeight : viewHeight - normalY;            if (height > viewHeight) {                returnY = y - normalY;            } else if (max < height) {                returnY = y - (height - max);            } else if((y + height) > viewportHeight){ // keep the document from scrolling                returnY = viewportHeight - height;            }        }        me.el.setY(returnY);    }});</pre></body></html>
 |