/**
 * A base class for all menu items that require menu-related functionality such as click handling,
 * sub-menus, icons, etc.
 *
 *     @example
 *     Ext.create('Ext.menu.Menu', {
 *         width: 100,
 *         height: 100,
 *         floating: false,  // usually you want this set to True (default)
 *         renderTo: Ext.getBody(),  // usually rendered by it's containing component
 *         items: [{
 *             text: 'icon item',
 *             iconCls: 'add16'
 *         }, {
 *             text: 'text item'
 *         }, {
 *             text: 'plain item',
 *             plain: true
 *         }]
 *     });
 */
Ext.define('Ext.menu.Item', {
    extend: 'Ext.Component',
    alias: 'widget.menuitem',
    alternateClassName: 'Ext.menu.TextItem',
 
    /**
     * @property {Boolean} isMenuItem
     * `true` in this class to identify an object as an instantiated Menu Item, or subclass thereof.
     */
    isMenuItem: true,
 
    mixins: [
        'Ext.mixin.Queryable'
    ],
 
    requires: [
        'Ext.Glyph'
    ],
 
    config: {
        /**
         * @cfg glyph
         * @inheritdoc Ext.panel.Header#cfg-glyph
         */
        glyph: null
    },
 
    /**
     * @property {Boolean} activated
     * Whether or not this item is currently activated
     */
    activated: false,
 
    /**
     * @property {Ext.menu.Menu} parentMenu
     * The parent Menu of this item.
     */
 
    /**
     * @cfg {String} activeCls
     * The CSS class added to the menu item when the item is focused.
     */
    activeCls: Ext.baseCSSPrefix + 'menu-item-active',
 
    /**
     * @cfg {Boolean} canActivate
     * Whether or not this menu item can be focused.
     * @deprecated 5.1.0 Use the {@link #focusable} config.
     */
 
    /**
     * @cfg {Number} clickHideDelay
     * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
     * This only has an effect when `hideOnClick: true`.
     */
    clickHideDelay: 0,
 
    /**
     * @cfg {Boolean} destroyMenu
     * Whether or not to destroy any associated sub-menu when this item is destroyed.
     */
    destroyMenu: true,
 
    /**
     * @cfg {String} disabledCls
     * The CSS class added to the menu item when the item is disabled.
     */
    disabledCls: Ext.baseCSSPrefix + 'menu-item-disabled',
 
    /**
     * @cfg {String} emptyText
     * The text to display when the the {@link #text} is empty.
     * @since 7.2.0
     */
    emptyText: '\u00a0',
 
    /**
     * @cfg {String} [href='#']
     * The href attribute to use for the underlying anchor link.
     */
 
    /**
     * @cfg {String} hrefTarget
     * The target attribute to use for the underlying anchor link.
     */
 
    /**
     * @cfg {Boolean} hideOnClick
     * Whether to not to hide the owning menu when this item is clicked.
     */
    hideOnClick: true,
 
    /**
     * @cfg [icon=Ext#BLANK_IMAGE_URL]
     * @inheritdoc Ext.panel.Header#cfg-icon
     */
 
    /**
     * @cfg iconCls
     * @inheritdoc Ext.panel.Header#cfg-iconCls
     */
 
    /**
     * @cfg {Ext.menu.Menu/Object} menu
     * Either an instance of {@link Ext.menu.Menu} or a config object for an {@link Ext.menu.Menu}
     * which will act as a sub-menu to this item.
     */
 
    /**
     * @property {Ext.menu.Menu} menu The sub-menu associated with this item, if one was configured.
     */
 
    /**
     * @cfg {String} menuAlign
     * The default {@link Ext.util.Positionable#getAlignToXY Ext.util.Positionable.getAlignToXY}
     * anchor position value for this item's sub-menu relative to this item's position.
     */
    menuAlign: 'tl-tr?',
 
    /**
     * @cfg {Number} menuExpandDelay
     * The delay in milliseconds before this item's sub-menu expands after this item is moused over.
     */
    menuExpandDelay: 200,
 
    /**
     * @cfg {Number} menuHideDelay
     * The delay in milliseconds before this item's sub-menu hides after this item is moused out.
     */
    menuHideDelay: 200,
 
    /**
     * @cfg {Boolean} plain
     * Whether or not this item is plain text/html with no icon or visual submenu indication.
     */
 
    /**
     * @cfg {String/Object} tooltip
     * The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted)
     * or QuickTips config object.
     */
 
    /**
     * @cfg {String} tooltipType
     * The type of tooltip to use. Either 'qtip' for QuickTips or 'title' for title attribute.
     */
    tooltipType: 'qtip',
 
    /**
     * @property focusable
     * @inheritdoc
     */
    focusable: true,
 
    /**
     * @property ariaRole
     * @inheritdoc
     */
    ariaRole: 'menuitem',
 
    /**
     * @property ariaEl
     * @inheritdoc
     */
    ariaEl: 'itemEl',
 
    /**
     * @cfg baseCls
     * @inheritdoc
     */
    baseCls: Ext.baseCSSPrefix + 'menu-item',
    arrowCls: Ext.baseCSSPrefix + 'menu-item-arrow',
    baseIconCls: Ext.baseCSSPrefix + 'menu-item-icon',
    textCls: Ext.baseCSSPrefix + 'menu-item-text',
    indentCls: Ext.baseCSSPrefix + 'menu-item-indent',
    indentNoSeparatorCls: Ext.baseCSSPrefix + 'menu-item-indent-no-separator',
    indentRightIconCls: Ext.baseCSSPrefix + 'menu-item-indent-right-icon',
    indentRightArrowCls: Ext.baseCSSPrefix + 'menu-item-indent-right-arrow',
    linkCls: Ext.baseCSSPrefix + 'menu-item-link',
    linkHrefCls: Ext.baseCSSPrefix + 'menu-item-link-href',
 
    /**
     * @cfg childEls
     * @inheritdoc
     */
    childEls: [
        'itemEl', 'iconEl', 'textEl', 'arrowEl'
    ],
 
    /* eslint-disable indent, max-len */
    /**
     * @cfg renderTpl
     * @inheritdoc
     */
    renderTpl:
        '<tpl if="plain">' +
            '{text}' +
        '<tpl else>' +
            '<a id="{id}-itemEl" data-ref="itemEl"' +
                ' class="{linkCls}<tpl if="hasHref"> {linkHrefCls}</tpl>{childElCls}"' +
                ' href="{href}" ' +
                '<tpl if="hrefTarget"> target="{hrefTarget}"</tpl>' +
                ' hidefocus="true"' +
                // For most browsers the text is already unselectable but Opera needs an explicit unselectable="on".
                ' unselectable="on"' +
                '<tpl if="tabIndex != null">' +
                    ' tabindex="{tabIndex}"' +
                '</tpl>' +
                '<tpl foreach="ariaAttributes"> {$}="{.}"</tpl>' +
            '>' +
                '<span id="{id}-textEl" data-ref="textEl" class="{textCls} {textCls}-{ui} {indentCls}{childElCls}" unselectable="on" role="presentation">{text}</span>' +
                '<tpl if="hasIcon">' +
                    '<div role="presentation" id="{id}-iconEl" data-ref="iconEl" class="{baseIconCls}-{ui} {baseIconCls}' +
                        '{[values.rightIcon ? "-right" : ""]} {iconCls}' +
                        '{childElCls} {glyphCls}" style="<tpl if="icon">background-image:url({icon});</tpl>' +
                        '<tpl if="glyph">' +
                            '<tpl if="glyphFontFamily">' +
                                'font-family:{glyphFontFamily};' +
                            '</tpl>' +
                            '">' +
                            '{glyph}' +
                        '<tpl else>' +
                            '">' +
                        '</tpl>' +
                    '</div>' +
                '</tpl>' +
                '<tpl if="showCheckbox">' +
                    '<div role="presentation" id="{id}-checkEl" data-ref="checkEl" class="{baseIconCls}-{ui} {baseIconCls}' +
                        '{[(values.hasIcon && !values.rightIcon) ? "-right" : ""]} ' +
                        '{groupCls} {checkboxCls}{childElCls}">' +
                    '</div>' +
                '</tpl>' +
                '<tpl if="hasMenu">' +
                    '<div role="presentation" id="{id}-arrowEl" data-ref="arrowEl" class="{arrowCls} {arrowCls}-{ui}{childElCls}"></div>' +
                '</tpl>' +
            '</a>' +
        '</tpl>',
    /* eslint-enable indent, max-len */
 
    /**
     * @cfg autoEl
     * @inheritdoc
     */
    autoEl: {
        role: 'presentation'
    },
 
    /**
     * @property maskOnDisable
     * @inheritdoc
     */
    maskOnDisable: false,
 
    iconAlign: 'left',
 
    /**
     * @cfg {String} text
     * The text/html to display in this item.
     */
 
    /**
     * @cfg {Function/String} handler
     * A function called when the menu item is clicked (can be used instead of {@link #click}
     * event).
     * @cfg {Ext.menu.Item} handler.item The item that was clicked
     * @cfg {Ext.event.Event} handler.e The underlying {@link Ext.event.Event}.
     * @controllable
     */
 
    /**
     * @event activate
     * Fires when this item is activated
     * @param {Ext.menu.Item} item The activated item
     */
 
    /**
     * @event click
     * Fires when this item is clicked
     * @param {Ext.menu.Item} item The item that was clicked
     * @param {Ext.event.Event} e The underlying {@link Ext.event.Event}.
     */
 
    /**
     * @event deactivate
     * Fires when this item is deactivated
     * @param {Ext.menu.Item} item The deactivated item
     */
 
    /**
     * @event textchange
     * Fired when the item's text is changed by the {@link #setText} method.
     * @param {Ext.menu.Item} this 
     * @param {String} oldText 
     * @param {String} newText 
     */
 
    /**
     * @event iconchange
     * Fired when the item's icon is changed by the {@link #setIcon} or {@link #setIconCls} methods.
     * @param {Ext.menu.Item} this 
     * @param {String} oldIcon 
     * @param {String} newIcon 
     */
 
    initComponent: function() {
        var me = this,
            cls = me.cls ? [me.cls] : [],
            menu;
 
        // During deprecation period of canActivate config, copy it into focusable config.
        if (me.hasOwnProperty('canActivate')) {
            me.focusable = me.canActivate;
        }
 
        if (me.plain) {
            cls.push(Ext.baseCSSPrefix + 'menu-item-plain');
        }
 
        if (cls.length) {
            me.cls = cls.join(' ');
        }
 
        if (me.menu) {
            menu = me.menu;
            me.menu = null;
            me.setMenu(menu);
        }
 
        me.callParent(arguments);
    },
 
    canFocus: function() {
        var me = this;
 
        // This is an override of the implementation in Focusable.
        // We do not refuse focus if the Item is disabled.
        // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu
        // "Disabled menu items receive focus but have no action when Enter or
        // Left Arrow/Right Arrow is pressed."
        // Test that deprecated canActivate config has not been set to false.
        return me.focusable && me.rendered && me.canActivate !== false &&
               !me.destroying && !me.destroyed &&
               me.isVisible(true);
    },
 
    onFocus: function(e) {
        var me = this;
 
        me.callParent([e]);
 
        // We do not refuse activation if the Item is disabled.
        // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu
        // "Disabled menu items receive focus but have no action when Enter or
        // Left Arrow/Right Arrow is pressed."
        if (!me.plain) {
            me.addCls(me.activeCls);
        }
 
        me.activated = true;
 
        if (me.hasListeners.activate) {
            me.fireEvent('activate', me);
        }
    },
 
    onFocusLeave: function(e) {
        var me = this;
 
        me.callParent([e]);
 
        if (!me.plain) {
            me.removeCls(me.activeCls);
        }
 
        me.doHideMenu();
        me.activated = false;
 
        if (me.hasListeners.deactivate) {
            me.fireEvent('deactivate', me);
        }
    },
 
    doHideMenu: function() {
        var menu = this.menu;
 
        this.cancelDeferExpand();
 
        if (menu && menu.isVisible()) {
            menu.hide();
        }
    },
 
    /**
     * @private
     * Hides the entire floating menu tree that we are within.
     * Walks up the refOwner axis hiding each Menu instance it find until it hits
     * a non-floating ancestor.
     */
    deferHideParentMenus: function() {
        var menu;
 
        // eslint-disable-next-line max-len
        for (menu = this.getRefOwner(); menu && ((menu.isMenu && menu.floating) || menu.isMenuItem); menu = menu.getRefOwner()) {
            if (menu.isMenu) {
                menu.hide();
            }
        }
    },
 
    expandMenu: function(event, delay) {
        var me = this;
 
        // An item can be focused (active), but disabled.
        // Disabled items must not action on click (or up/down arrow)
        // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu
        // "Disabled menu items receive focus but have no action when Enter or
        // Left Arrow/Right Arrow is pressed."
        if (!me.disabled && me.activated && me.menu) {
 
            // hideOnClick makes no sense when there's a child menu
            me.hideOnClick = false;
 
            me.cancelDeferHide();
 
            // Allow configuration of zero to perform immediate expansion.
            delay = delay == null ? me.menuExpandDelay : delay;
 
            if (delay === 0) {
                me.doExpandMenu(event);
            }
            else {
                me.cancelDeferExpand();
                // Delay can't be 0 by this point
                me.expandMenuTimer = Ext.defer(me.doExpandMenu, delay, me, [event]);
            }
        }
    },
 
    doExpandMenu: function(clickEvent) {
        var me = this,
            menu = me.menu,
            ariaDom;
 
        if (!menu.isVisible()) {
            me.parentMenu.activeChild = menu;
            menu.ownerCmp = me;
            menu.parentMenu = me.parentMenu;
            menu.constrainTo = document.body;
 
            // Pointer-invoked menus do not auto focus, key invoked ones do.
            menu.autoFocus = !clickEvent || !clickEvent.pointerType;
            menu.showBy(me, me.menuAlign);
            ariaDom = me.ariaEl.dom;
 
            if (ariaDom) {
                ariaDom.setAttribute('aria-owns', menu.id);
            }
        }
        // Keyboard events should focus the first menu item even if it was already expanded
        else if (clickEvent && clickEvent.type === 'keydown') {
            menu.focus();
        }
    },
 
    getRefItems: function(deep) {
        var menu = this.menu,
            items;
 
        if (menu) {
            items = menu.getRefItems(deep);
            items.unshift(menu);
        }
 
        return items || [];
    },
 
    getValue: function() {
        return this.value;
    },
 
    hideMenu: function(delay) {
        var me = this;
 
        if (me.menu) {
            me.cancelDeferExpand();
            me.hideMenuTimer = Ext.defer(
                me.doHideMenu, Ext.isNumber(delay) ? delay : me.menuHideDelay, me
            );
        }
    },
 
    onClick: function(e) {
        var me = this,
            clickHideDelay = me.clickHideDelay,
            browserEvent = e.browserEvent,
            clickResult, preventDefault;
 
        if (!me.href || me.disabled) {
            e.stopEvent();
 
            if (me.disabled) {
                return false;
            }
        }
 
        if (me.disabled || me.handlingClick) {
            return;
        }
 
        if (me.hideOnClick && !me.menu) {
            // on mobile webkit, when the menu item has an href, a longpress will 
            // trigger the touch call-out menu to show.  If this is the case, the tap 
            // event object's browser event type will be 'touchcancel', and we do not 
            // want to hide the menu.
 
            // items with submenus are activated by touchstart on mobile browsers, so
            // we cannot hide the menu on "tap"
            if (!clickHideDelay) {
                me.deferHideParentMenus();
            }
            else {
                me.deferHideParentMenusTimer =
                    Ext.defer(me.deferHideParentMenus, clickHideDelay, me);
            }
        }
 
        // Click event may have destroyed the menu, don't do anything further
        clickResult = me.fireEvent('click', me, e);
 
        // Click listener could have destroyed the menu and/or item.
        if (me.destroyed) {
            return;
        }
 
        if (clickResult !== false && me.handler) {
            Ext.callback(me.handler, me.scope, [me, e], 0, me);
        }
 
        // And the handler could have done the same. We check this twice
        // because if the menu was destroyed in the click listener, the handler
        // should not have been called.
        if (me.destroyed) {
            return;
        }
 
        // If there's an href, invoke dom.click() after we've fired the click event in case a click
        // listener wants to handle it.
        //
        // Note that we're having to do this because the key navigation code will blindly call
        // stopEvent() on all key events that it handles!
        //
        // But, we need to check the browser event object that was passed to the listeners
        // to determine if the default action has been prevented.
        // If so, we don't want to honor the .href config.
        if (Ext.isIE9m) {
            // Here we need to invert the value since it's meaning is the opposite
            // of defaultPrevented.
            preventDefault = browserEvent.returnValue === false ? true : false;
        }
        else {
            preventDefault = !!browserEvent.defaultPrevented;
        }
 
        // We only manually need to trigger the click event if it's come from a key event.
        if (me.href && e.type !== 'click' && !preventDefault) {
            me.handlingClick = true;
            me.itemEl.dom.click();
            me.handlingClick = false;
        }
 
        if (!me.hideOnClick && !me.hasFocus) {
            me.focus();
        }
 
        return clickResult;
    },
 
    onRemoved: function() {
        var me = this;
 
        // Removing the active item, must deactivate it.
        if (me.activated && me.parentMenu.activeItem === me) {
            me.parentMenu.deactivateActiveItem();
        }
 
        me.callParent(arguments);
        me.parentMenu = me.ownerCmp = null;
    },
 
    doDestroy: function() {
        var me = this;
 
        if (me.rendered) {
            me.clearTip();
        }
 
        me.cancelDeferExpand();
        me.cancelDeferHide();
        Ext.undefer(me.deferHideParentMenusTimer);
 
        me.setMenu(null);
 
        me.callParent();
    },
 
    beforeRender: function() {
        var me = this,
            glyph = me.glyph,
            glyphFontFamily,
            hasIcon = !!(me.icon || me.iconCls || glyph),
            hasMenu = !!me.menu,
            rightIcon = ((me.iconAlign === 'right') && !hasMenu),
            isCheckItem = me.isMenuCheckItem,
            indentCls = [],
            ownerCt = me.ownerCt,
            isOwnerPlain = ownerCt.plain;
 
        if (me.plain) {
            me.ariaEl = 'el';
        }
 
        me.callParent();
 
        if (hasIcon) {
            if (hasMenu && me.showCheckbox) {
                // nowhere to put the icon, menu arrow on one side, checkbox on the other.
                // TODO:  maybe put the icon or checkbox next to the arrow?
                hasIcon = false;
            }
        }
 
        // Transform Glyph to the useful parts
        if (glyph) {
            glyphFontFamily = glyph.fontFamily;
            glyph = glyph.character;
        }
 
        if (!isOwnerPlain || (hasIcon && !rightIcon) || isCheckItem) {
            if (ownerCt.showSeparator && !isOwnerPlain) {
                indentCls.push(me.indentCls);
            }
            else {
                indentCls.push(me.indentNoSeparatorCls);
            }
        }
 
        if (hasMenu) {
            indentCls.push(me.indentRightArrowCls);
        }
        else if (hasIcon && (rightIcon || isCheckItem)) {
            indentCls.push(me.indentRightIconCls);
        }
 
        Ext.applyIf(me.renderData, {
            hasHref: !!me.href,
            href: me.href || '#',
            hrefTarget: me.hrefTarget,
            icon: me.icon,
            iconCls: me.iconCls,
            glyph: glyph,
            glyphCls: glyph ? Ext.baseCSSPrefix + 'menu-item-glyph' : undefined,
            glyphFontFamily: glyphFontFamily,
            hasIcon: hasIcon,
            hasMenu: hasMenu,
            indent: !isOwnerPlain || hasIcon || isCheckItem,
            isCheckItem: isCheckItem,
            rightIcon: rightIcon,
            plain: me.plain,
            text: me.getDisplayText(),
            arrowCls: me.arrowCls,
            baseIconCls: me.baseIconCls,
            textCls: me.textCls,
            indentCls: indentCls.join(' '),
            linkCls: me.linkCls,
            linkHrefCls: me.linkHrefCls,
            groupCls: me.group ? me.groupCls : '',
            tabIndex: me.tabIndex
        });
    },
 
    onRender: function() {
        var me = this;
 
        me.callParent(arguments);
 
        if (me.tooltip) {
            me.setTooltip(me.tooltip, true);
        }
    },
 
    /**
     * Get the attached sub-menu for this item.
     * @return {Ext.menu.Menu} The sub-menu. `null` if it doesn't exist.
     */
    getMenu: function() {
        return this.menu || null;
    },
 
    /**
     * Set a child menu for this item. See the {@link #cfg-menu} configuration.
     * @param {Ext.menu.Menu/Object} menu A menu, or menu configuration. null may be
     * passed to remove the menu.
     * @param {Boolean} [destroyMenu] True to destroy any existing menu. False to
     * prevent destruction. If not specified, the {@link #destroyMenu} configuration
     * will be used.
     */
    setMenu: function(menu, destroyMenu) {
        var me = this,
            oldMenu = me.menu,
            arrowEl = me.arrowEl,
            ariaDom = me.ariaEl.dom,
            ariaAttr, instanced;
 
        if (oldMenu) {
            oldMenu.ownerCmp = oldMenu.parentMenu = null;
 
            if (destroyMenu === true || (destroyMenu !== false && me.destroyMenu)) {
                Ext.destroy(oldMenu);
            }
 
            if (ariaDom) {
                ariaDom.removeAttribute('aria-haspopup');
                ariaDom.removeAttribute('aria-owns');
            }
            else {
                ariaAttr = (me.ariaRenderAttributes || (me.ariaRenderAttributes = {}));
 
                delete ariaAttr['aria-haspopup'];
                delete ariaAttr['aria-owns'];
            }
        }
 
        if (menu) {
            instanced = menu.isMenu;
            menu = me.menu = Ext.menu.Manager.get(menu, {
                ownerCmp: me,
                focusOnToFront: false
            });
            // We need to forcibly set this here because we could be passed
            // an existing menu, which means the config above won't get applied
            // during creation.
            menu.setOwnerCmp(me, instanced);
 
            if (ariaDom) {
                ariaDom.setAttribute('aria-haspopup', true);
                ariaDom.setAttribute('aria-owns', menu.id);
            }
            else {
                ariaAttr = (me.ariaRenderAttributes || (me.ariaRenderAttributes = {}));
 
                ariaAttr['aria-haspopup'] = true;
 
                if (!menu.hidden) {
                    ariaAttr['aria-owns'] = menu.id;
                }
            }
        }
        else {
            menu = me.menu = null;
        }
 
        if (menu && me.rendered && !me.destroying && arrowEl) {
            arrowEl[menu ? 'addCls' : 'removeCls'](me.arrowCls);
        }
    },
 
    /**
     * Sets the {@link #click} handler of this item
     * @param {Function} fn The handler function
     * @param {Object} [scope] The scope of the handler function
     */
    setHandler: function(fn, scope) {
        this.handler = fn || null;
        this.scope = scope;
    },
 
    /**
     * Sets the {@link #icon} on this item.
     * @param {String} icon The new icon URL. If this `MenuItem` was configured with a
     * {@link #cfg-glyph}, this may be a glyph configuration. See {@link #cfg-glyph}.
     */
    setIcon: function(icon) {
        var me = this,
            iconEl = me.iconEl,
            oldIcon = me.icon;
 
        // If setIcon is called when we are configured with a glyph, clear the glyph
        if (me.glyph) {
            me.setGlyph(null);
        }
 
        if (iconEl) {
            iconEl.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
        }
 
        me.icon = icon;
        me.fireEvent('iconchange', me, oldIcon, icon);
    },
 
    /**
     * Sets the {@link #iconCls} of this item
     * @param {String} iconCls The CSS class to set to {@link #iconCls}
     */
    setIconCls: function(iconCls) {
        var me = this,
            iconEl = me.iconEl,
            oldCls = me.iconCls;
 
        // If setIcon is called when we are configured with a glyph, clear the glyph
        if (me.glyph) {
            me.setGlyph(null);
        }
 
        if (iconEl) {
            // In case it had been set to 'none' by a glyph setting.
            iconEl.setStyle('background-image', '');
 
            if (me.iconCls) {
                iconEl.removeCls(me.iconCls);
            }
 
            if (iconCls) {
                iconEl.addCls(iconCls);
            }
        }
 
        me.iconCls = iconCls;
        me.fireEvent('iconchange', me, oldCls, iconCls);
    },
 
    /**
     * Sets the {@link #text} of this item
     * @param {String} text The {@link #text}
     */
    setText: function(text) {
        var me = this,
            el = me.textEl || me.el,
            oldText = me.text;
 
        me.text = text;
 
        if (me.rendered) {
            el.setHtml(me.getDisplayText());
            me.updateLayout();
        }
 
        me.fireEvent('textchange', me, oldText, text);
    },
 
    getTipAttr: function() {
        return this.tooltipType === 'qtip' ? 'data-qtip' : 'title';
    },
 
    /**
     * @private
     */
    clearTip: function() {
        if (Ext.quickTipsActive && Ext.isObject(this.tooltip)) {
            Ext.tip.QuickTipManager.unregister(this.itemEl);
        }
    },
 
    /**
     * Sets the tooltip for this menu item.
     *
     * @param {String/Object} tooltip This may be:
     *
     *   - **String** : A string to be used as innerHTML (html tags are accepted) to show
     *     in a tooltip
     *   - **Object** : A configuration object for {@link Ext.tip.QuickTipManager#register}.
     *
     * @param {Boolean} [initial] (private)
     *
     * @return {Ext.menu.Item} this
     */
    setTooltip: function(tooltip, initial) {
        var me = this;
 
        if (me.rendered) {
            if (!initial) {
                me.clearTip();
            }
 
            if (Ext.quickTipsActive && Ext.isObject(tooltip)) {
                Ext.tip.QuickTipManager.register(
                    Ext.apply({
                        target: me.itemEl.id
                    }, tooltip)
                );
                me.tooltip = tooltip;
            }
            else {
                me.itemEl.dom.setAttribute(me.getTipAttr(), tooltip);
            }
        }
        else {
            me.tooltip = tooltip;
        }
 
        return me;
    },
 
    getFocusEl: function() {
        return this.plain ? this.el : this.itemEl;
    },
 
    getFocusClsEl: function() {
        return this.el;
    },
 
    getDisplayText: function() {
        return this.text || this.emptyText;
    },
 
    privates: {
        cancelDeferExpand: function() {
            window.clearTimeout(this.expandMenuTimer);
        },
 
        cancelDeferHide: function() {
            window.clearTimeout(this.hideMenuTimer);
        }
    },
 
    applyGlyph: function(glyph, oldGlyph) {
        if (glyph) {
            if (!glyph.isGlyph) {
                glyph = new Ext.Glyph(glyph);
            }
 
            if (glyph.isEqual(oldGlyph)) {
                glyph = undefined;
            }
        }
 
        return glyph;
    },
 
    updateGlyph: function(glyph, oldGlyph) {
        var iconEl = this.iconEl;
 
        if (iconEl) {
            iconEl.setStyle('background-image', 'none');
            this.icon = null;
 
            if (glyph) {
                iconEl.dom.innerHTML = glyph.character;
                iconEl.setStyle(glyph.getStyle());
            }
            else {
                iconEl.dom.innerHTML = '';
            }
        }
    }
});