/** * @class Ext.mixin.Focusable */ Ext.define('Ext.overrides.mixin.Focusable', { override: 'Ext.Component', /** * @cfg {String} [focusCls='focus'] CSS class suffix that will be used to * compose the CSS class name that will be added to Component's {@link #focusClsEl}, * and removed when Component blurs. * * **Note** that this is not a full CSS class name; this suffix will be combined * with component's UI class via {@link #addClsWithUI} and {@link #removeClsWithUI} methods. */ focusCls: 'focus', /** * Try to focus this component. * * If this component is disabled, a close relation will be targeted for focus instead * to keep focus localized for keyboard users. * @param {Mixed} [selectText] If applicable, `true` to also select all the text in this component, or an array consisting of start and end (defaults to start) position of selection. * @param {Boolean/Number} [delay] Delay the focus this number of milliseconds (true for 10 milliseconds). * @param {Function} [callback] Only needed if the `delay` parameter is used. A function to call upon focus. * @param {Function} [scope] Only needed if the `delay` parameter is used. The scope (`this` reference) in which to execute the callback. * @return {Ext.Component} The focused Component. Usually `this` Component. Some Containers may * delegate focus to a descendant Component ({@link Ext.window.Window Window}s can do this through their * {@link Ext.window.Window#defaultFocus defaultFocus} config option. If this component is disabled, a closely * related component will be focused and that will be returned. */ focus: function(selectText, delay, callback, scope) { var me = this, containerScrollTop; if ((!me.focusable && !me.isContainer) || me.destroyed || me.destroying) { return me; } // If delay is wanted, queue a call to this function. if (delay) { me.getFocusTask().delay(Ext.isNumber(delay) ? delay : 10, me.focus, me, [selectText, false, callback, scope]); return me; } // An immediate focus call must cancel any outstanding delayed focus calls. me.cancelFocus(); if (me.floating && me.container && me.container.dom) { containerScrollTop = me.container.dom.scrollTop; } // Core Focusable method will return true if focusing was attempted if (me.mixins.focusable.focus.apply(me, arguments) !== false) { if (callback) { Ext.callback(callback, scope); } // Focusing a floating Component brings it to the front of its stack. // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion. if (me.floating && containerScrollTop !== undefined) { me.container.dom.scrollTop = containerScrollTop; } } return me; }, /** * Cancel any deferred focus on this component * @protected */ cancelFocus: function() { var me = this, task = me.getFocusTask(); if (task) { task.cancel(); } }, /** * @method * Template method to do any pre-blur processing. * @protected * @param {Ext.event.Event} e The event object */ beforeBlur: Ext.emptyFn, /** * @method * Template method to do any post-blur processing. * @protected * @param {Ext.event.Event} e The event object */ postBlur: Ext.emptyFn, /** * @method * Template method to do any pre-focus processing. * @protected * @param {Ext.event.Event} e The event object */ beforeFocus: Ext.emptyFn, /** * @method * Template method to do any post-focus processing. * @protected * @param {Ext.event.Event} e The event object */ postFocus: Ext.emptyFn, onFocusEnter: function(e) { var me = this; if (me.destroying || me.destroyed) { return; } // Focusing must being a floating component to the front. // Only bring to front if this component is not the manager's // topmost component (may be a result of focusOnToFront). if (me.floating && me !== me.zIndexManager.getActive()) { me.toFront(true); } me.callParent([e]); }, destroyFocusable: function() { var me = this; // Calling cancelFocus() will assign focusTask property, // which we don't want during destruction if (me.focusTask) { me.focusTask.stop(me.focus, me); } me.callParent(); }, privates: { addFocusCls: function(e) { var me = this, focusCls = me.focusCls, el; if (focusCls) { el = me.getFocusClsEl(e); if (el) { el.addCls(me.addClsWithUI(focusCls, true)); } } }, removeFocusCls: function(e) { var me = this, focusCls = me.focusCls, el; if (focusCls) { el = me.getFocusClsEl(e); if (el) { el.removeCls(me.removeClsWithUI(focusCls, true)); } } }, /** * @private */ getFocusTask: function() { if (!this.focusTask) { this.focusTask = Ext.focusTask; } return this.focusTask; }, updateMaskState: function(state, mask) { var me = this, ariaEl = me.ariaEl.dom, value; if (state) { me.disableTabbing(); me.setMasked(true); if (ariaEl) { ariaEl.setAttribute('aria-busy', 'true'); // It is possible that ariaEl already has aria-describedby attribute; // in that case we need to save it to restore later. value = ariaEl.getAttribute('aria-describedby'); if (value) { me._savedAriaDescribedBy = value; } ariaEl.setAttribute('aria-describedby', mask.ariaEl.id); } } else { me.enableTabbing(); me.setMasked(false); if (ariaEl) { ariaEl.removeAttribute('aria-busy'); value = ariaEl.getAttribute('aria-describedby'); ariaEl.removeAttribute('aria-describedby'); if (value === mask.ariaEl.id && me._savedAriaDescribedBy) { ariaEl.setAttribute('aria-describedby', me._savedAriaDescribedBy); delete me._savedAriaDescribedBy; } } } } }}, function() { // One global DelayedTask to assign focus // So that the last focus call wins. if (!Ext.focusTask) { Ext.focusTask = new Ext.util.DelayedTask(); }});