@qooxdoo/framework
Version:
The JS Framework for Coders
1,578 lines (1,354 loc) • 43.2 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2007-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Fabian Jakobs (fjakobs)
* Christian Hagendorn (chris_schmidt)
************************************************************************ */
/**
* This handler is used to normalize all focus/activation requirements
* and normalize all cross browser quirks in this area.
*
* Notes:
*
* * Webkit and Opera (before 9.5) do not support tabIndex for all elements
* (See also: https://bugs.webkit.org/show_bug.cgi?id=7138)
*
* * TabIndex is normally 0, which means all naturally focusable elements are focusable.
* * TabIndex > 0 means that the element is focusable and tabable
* * TabIndex < 0 means that the element, even if naturally possible, is not focusable.
*
* NOTE: Instances of this class must be disposed of after use
*
* @use(qx.event.dispatch.DomBubbling)
*/
qx.Class.define("qx.event.handler.Focus", {
extend: qx.core.Object,
implement: [qx.event.IEventHandler, qx.core.IDisposable],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* Create a new instance
*
* @param manager {qx.event.Manager} Event manager for the window to use
*
* @ignore(qx.application.Inline)
*/
construct(manager) {
super();
// Define shorthands
this._manager = manager;
this._window = manager.getWindow();
this._document = this._window.document;
this._root = this._document.documentElement;
this._body = this._document.body;
if (
qx.core.Environment.get("os.name") == "ios" &&
parseFloat(qx.core.Environment.get("os.version")) > 6 &&
parseFloat(qx.core.Environment.get("os.version")) < 15 &&
(!qx.application.Inline ||
!qx.core.Init.getApplication() instanceof qx.application.Inline)
) {
this.__needsScrollFix = true;
}
// Initialize
this._initObserver();
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
/** The active DOM element */
active: {
apply: "_applyActive",
nullable: true
},
/** The focused DOM element */
focus: {
apply: "_applyFocus",
nullable: true
}
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics: {
/** @type {Integer} Priority of this handler */
PRIORITY: qx.event.Registration.PRIORITY_NORMAL,
/** @type {Map} Supported event types */
SUPPORTED_TYPES: {
focus: 1,
blur: 1,
focusin: 1,
focusout: 1,
activate: 1,
deactivate: 1
},
/** @type {Integer} Whether the method "canHandleEvent" must be called */
IGNORE_CAN_HANDLE: true,
/**
* @type {Map} See: http://msdn.microsoft.com/en-us/library/ms534654(VS.85).aspx
*/
FOCUSABLE_ELEMENTS: qx.core.Environment.select("engine.name", {
mshtml: {
a: 1,
body: 1,
button: 1,
frame: 1,
iframe: 1,
img: 1,
input: 1,
object: 1,
select: 1,
textarea: 1
},
gecko: {
a: 1,
body: 1,
button: 1,
frame: 1,
iframe: 1,
img: 1,
input: 1,
object: 1,
select: 1,
textarea: 1
},
opera: {
button: 1,
input: 1,
select: 1,
textarea: 1
},
webkit: {
button: 1,
input: 1,
select: 1,
textarea: 1
}
})
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
__onNativeMouseDownWrapper: null,
__onNativeMouseUpWrapper: null,
__onNativeFocusWrapper: null,
__onNativeBlurWrapper: null,
__onNativeDragGestureWrapper: null,
__onNativeSelectStartWrapper: null,
__onNativeFocusInWrapper: null,
__onNativeFocusOutWrapper: null,
__previousFocus: null,
__previousActive: null,
__down: "",
__up: "",
__needsScrollFix: false,
__relatedTarget: null,
/*
---------------------------------------------------------------------------
EVENT HANDLER INTERFACE
---------------------------------------------------------------------------
*/
// interface implementation
canHandleEvent(target, type) {},
// interface implementation
registerEvent(target, type, capture) {
// Nothing needs to be done here
},
// interface implementation
unregisterEvent(target, type, capture) {
// Nothing needs to be done here
},
/*
---------------------------------------------------------------------------
FOCUS/BLUR USER INTERFACE
---------------------------------------------------------------------------
*/
/**
* Focuses the given DOM element
*
* @param element {Element} DOM element to focus
*/
focus(element) {
// Fixed timing issue with IE, see [BUG #3267]
if (qx.core.Environment.get("engine.name") == "mshtml") {
window.setTimeout(function () {
try {
// focus element before set cursor position
element.focus();
// Fixed cursor position issue with IE, only when nothing is selected.
// See [BUG #3519] for details.
var selection = qx.bom.Selection.get(element);
if (
selection.length == 0 &&
typeof element.createTextRange == "function"
) {
var textRange = element.createTextRange();
textRange.moveStart("character", element.value.length);
textRange.collapse();
textRange.select();
}
} catch (ex) {}
}, 0);
} else {
// Fix re-focusing on mousup event
// See https://github.com/qooxdoo/qooxdoo/issues/9393 and
// discussion in https://github.com/qooxdoo/qooxdoo/pull/9394
window.setTimeout(function () {
try {
element.focus();
} catch (ex) {}
}, 0);
}
this.setFocus(element);
this.setActive(element);
},
/**
* Activates the given DOM element
*
* @param element {Element} DOM element to activate
*/
activate(element) {
this.setActive(element);
},
/**
* Blurs the given DOM element
*
* @param element {Element} DOM element to focus
*/
blur(element) {
try {
element.blur();
} catch (ex) {}
if (this.getActive() === element) {
this.resetActive();
}
if (this.getFocus() === element) {
this.resetFocus();
}
},
/**
* Deactivates the given DOM element
*
* @param element {Element} DOM element to activate
*/
deactivate(element) {
if (this.getActive() === element) {
this.resetActive();
}
},
/**
* Tries to activate the given element. This checks whether
* the activation is allowed first.
*
* @param element {Element} DOM element to activate
*/
tryActivate(element) {
var active = this.__findActivatableElement(element);
if (active) {
this.setActive(active);
}
},
/*
---------------------------------------------------------------------------
HELPER
---------------------------------------------------------------------------
*/
/**
* Shorthand to fire events from within this class.
*
* @param target {Element} DOM element which is the target
* @param related {Element} DOM element which is the related target
* @param type {String} Name of the event to fire
* @param bubbles {Boolean} Whether the event should bubble
* @return {qx.Promise?} a promise, if one or more of the event handlers returned a promise
*/
__fireEvent(target, related, type, bubbles) {
var Registration = qx.event.Registration;
var evt = Registration.createEvent(type, qx.event.type.Focus, [
target,
related,
bubbles
]);
return Registration.dispatchEvent(target, evt);
},
/*
---------------------------------------------------------------------------
WINDOW FOCUS/BLUR SUPPORT
---------------------------------------------------------------------------
*/
/** @type {Boolean} Whether the window is focused currently */
_windowFocused: true,
/**
* Helper for native event listeners to react on window blur
*/
__doWindowBlur() {
// Omit doubled blur events
// which is a common behavior at least for gecko based clients
if (this._windowFocused) {
this._windowFocused = false;
this.__fireEvent(this._window, null, "blur", false);
}
},
/**
* Helper for native event listeners to react on window focus
*/
__doWindowFocus() {
// Omit doubled focus events
// which is a common behavior at least for gecko based clients
if (!this._windowFocused) {
this._windowFocused = true;
this.__fireEvent(this._window, null, "focus", false);
}
},
/*
---------------------------------------------------------------------------
NATIVE OBSERVER
---------------------------------------------------------------------------
*/
/**
* Initializes event listeners.
*
* @signature function()
*/
_initObserver: qx.core.Environment.select("engine.name", {
gecko() {
// Bind methods
this.__onNativeMouseDownWrapper = qx.lang.Function.listener(
this.__onNativeMouseDown,
this
);
this.__onNativeMouseUpWrapper = qx.lang.Function.listener(
this.__onNativeMouseUp,
this
);
this.__onNativeFocusWrapper = qx.lang.Function.listener(
this.__onNativeFocus,
this
);
this.__onNativeBlurWrapper = qx.lang.Function.listener(
this.__onNativeBlur,
this
);
this.__onNativeDragGestureWrapper = qx.lang.Function.listener(
this.__onNativeDragGesture,
this
);
// Register events
qx.bom.Event.addNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
// Capturing is needed for gecko to correctly
// handle focus of input and textarea fields
qx.bom.Event.addNativeListener(
this._window,
"focus",
this.__onNativeFocusWrapper,
true
);
qx.bom.Event.addNativeListener(
this._window,
"blur",
this.__onNativeBlurWrapper,
true
);
// Capture drag events
qx.bom.Event.addNativeListener(
this._window,
"draggesture",
this.__onNativeDragGestureWrapper,
true
);
},
mshtml() {
// Bind methods
this.__onNativeMouseDownWrapper = qx.lang.Function.listener(
this.__onNativeMouseDown,
this
);
this.__onNativeMouseUpWrapper = qx.lang.Function.listener(
this.__onNativeMouseUp,
this
);
this.__onNativeFocusInWrapper = qx.lang.Function.listener(
this.__onNativeFocusIn,
this
);
this.__onNativeFocusOutWrapper = qx.lang.Function.listener(
this.__onNativeFocusOut,
this
);
this.__onNativeSelectStartWrapper = qx.lang.Function.listener(
this.__onNativeSelectStart,
this
);
// Register events
qx.bom.Event.addNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper
);
qx.bom.Event.addNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper
);
// MSHTML supports their own focusin and focusout events
// To detect which elements get focus the target is useful
// The window blur can detected using focusout and look
// for the toTarget property which is empty in this case.
qx.bom.Event.addNativeListener(
this._document,
"focusin",
this.__onNativeFocusInWrapper
);
qx.bom.Event.addNativeListener(
this._document,
"focusout",
this.__onNativeFocusOutWrapper
);
// Add selectstart to prevent selection
qx.bom.Event.addNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper
);
},
webkit: qx.core.Environment.select("browser.name", {
// fix for [ISSUE #9174]
// distinguish bettween MS Edge, which is reported
// as engine webkit and all other webkit browsers
edge(domEvent) {
// Bind methods
this.__onNativeMouseDownWrapper = qx.lang.Function.listener(
this.__onNativeMouseDown,
this
);
this.__onNativeMouseUpWrapper = qx.lang.Function.listener(
this.__onNativeMouseUp,
this
);
this.__onNativeFocusOutWrapper = qx.lang.Function.listener(
this.__onNativeFocusOut,
this
);
this.__onNativeFocusInWrapper = qx.lang.Function.listener(
this.__onNativeFocusIn,
this
);
this.__onNativeSelectStartWrapper = qx.lang.Function.listener(
this.__onNativeSelectStart,
this
);
// Register events
qx.bom.Event.addNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper,
false
);
qx.bom.Event.addNativeListener(
this._document,
"focusin",
this.__onNativeFocusInWrapper
);
qx.bom.Event.addNativeListener(
this._document,
"focusout",
this.__onNativeFocusOutWrapper
);
},
default(domEvent) {
// Bind methods
this.__onNativeMouseDownWrapper = qx.lang.Function.listener(
this.__onNativeMouseDown,
this
);
this.__onNativeMouseUpWrapper = qx.lang.Function.listener(
this.__onNativeMouseUp,
this
);
this.__onNativeFocusOutWrapper = qx.lang.Function.listener(
this.__onNativeFocusOut,
this
);
this.__onNativeFocusWrapper = qx.lang.Function.listener(
this.__onNativeFocus,
this
);
this.__onNativeBlurWrapper = qx.lang.Function.listener(
this.__onNativeBlur,
this
);
this.__onNativeSelectStartWrapper = qx.lang.Function.listener(
this.__onNativeSelectStart,
this
);
// Register events
qx.bom.Event.addNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper,
false
);
qx.bom.Event.addNativeListener(
this._window,
"DOMFocusOut",
this.__onNativeFocusOutWrapper,
true
);
qx.bom.Event.addNativeListener(
this._window,
"focus",
this.__onNativeFocusWrapper,
true
);
qx.bom.Event.addNativeListener(
this._window,
"blur",
this.__onNativeBlurWrapper,
true
);
}
}),
opera() {
// Bind methods
this.__onNativeMouseDownWrapper = qx.lang.Function.listener(
this.__onNativeMouseDown,
this
);
this.__onNativeMouseUpWrapper = qx.lang.Function.listener(
this.__onNativeMouseUp,
this
);
this.__onNativeFocusInWrapper = qx.lang.Function.listener(
this.__onNativeFocusIn,
this
);
this.__onNativeFocusOutWrapper = qx.lang.Function.listener(
this.__onNativeFocusOut,
this
);
// Register events
qx.bom.Event.addNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.addNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.addNativeListener(
this._window,
"DOMFocusIn",
this.__onNativeFocusInWrapper,
true
);
qx.bom.Event.addNativeListener(
this._window,
"DOMFocusOut",
this.__onNativeFocusOutWrapper,
true
);
}
}),
/**
* Disconnects event listeners.
*
* @signature function()
*/
_stopObserver: qx.core.Environment.select("engine.name", {
gecko() {
qx.bom.Event.removeNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"focus",
this.__onNativeFocusWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"blur",
this.__onNativeBlurWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"draggesture",
this.__onNativeDragGestureWrapper,
true
);
},
mshtml() {
qx.bom.Event.removeNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"focusin",
this.__onNativeFocusInWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"focusout",
this.__onNativeFocusOutWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper
);
},
webkit: qx.core.Environment.select("browser.name", {
// fix for [ISSUE #9174]
// distinguish bettween MS Edge, which is reported
// as engine webkit and all other webkit browsers
edge() {
qx.bom.Event.removeNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"focusin",
this.__onNativeFocusInWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"focusout",
this.__onNativeFocusOutWrapper
);
qx.bom.Event.removeNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper
);
},
default() {
qx.bom.Event.removeNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._document,
"selectstart",
this.__onNativeSelectStartWrapper,
false
);
qx.bom.Event.removeNativeListener(
this._window,
"DOMFocusOut",
this.__onNativeFocusOutWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"focus",
this.__onNativeFocusWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"blur",
this.__onNativeBlurWrapper,
true
);
}
}),
opera() {
qx.bom.Event.removeNativeListener(
this._document,
"mousedown",
this.__onNativeMouseDownWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._document,
"mouseup",
this.__onNativeMouseUpWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"DOMFocusIn",
this.__onNativeFocusInWrapper,
true
);
qx.bom.Event.removeNativeListener(
this._window,
"DOMFocusOut",
this.__onNativeFocusOutWrapper,
true
);
}
}),
/*
---------------------------------------------------------------------------
NATIVE LISTENERS
---------------------------------------------------------------------------
*/
/**
* Native event listener for <code>draggesture</code> event
* supported by gecko. Used to stop native drag and drop when
* selection is disabled.
*
* @see https://developer.mozilla.org/en-US/docs/Drag_and_Drop
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeDragGesture: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
gecko(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (!this.__isSelectable(target)) {
qx.bom.Event.preventDefault(domEvent);
}
},
default: null
})
),
/**
* Native event listener for <code>DOMFocusIn</code> or <code>focusin</code>
* depending on the client's engine.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeFocusIn: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(domEvent) {
// Force window focus to be the first
this.__doWindowFocus();
// Update internal data
var target = qx.bom.Event.getTarget(domEvent);
// IE focusin is also fired on elements which are not focusable at all
// We need to look up for the next focusable element.
var focusTarget = this.__findFocusableElement(target);
if (focusTarget) {
this.setFocus(focusTarget);
}
// Make target active
this.tryActivate(target);
},
webkit: qx.core.Environment.select("browser.name", {
// fix for [ISSUE #9174]
// distinguish bettween MS Edge, which is reported
// as engine webkit and all other webkit browsers
edge(domEvent) {
// Force window focus to be the first
this.__doWindowFocus();
// Update internal data
var target = qx.bom.Event.getTarget(domEvent);
// IE focusin is also fired on elements which are not focusable at all
// We need to look up for the next focusable element.
var focusTarget = this.__findFocusableElement(target);
if (focusTarget) {
this.setFocus(focusTarget);
}
// Make target active
this.tryActivate(target);
},
default: null
}),
opera(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target == this._document || target == this._window) {
this.__doWindowFocus();
if (this.__previousFocus) {
this.setFocus(this.__previousFocus);
delete this.__previousFocus;
}
if (this.__previousActive) {
this.setActive(this.__previousActive);
delete this.__previousActive;
}
} else {
this.setFocus(target);
this.tryActivate(target);
// Clear selection
if (!this.__isSelectable(target)) {
target.selectionStart = 0;
target.selectionEnd = 0;
}
}
},
default: null
})
),
/**
* Native event listener for <code>DOMFocusOut</code> or <code>focusout</code>
* depending on the client's engine.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeFocusOut: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(domEvent) {
var relatedTarget = qx.bom.Event.getRelatedTarget(domEvent);
// If the focus goes to nowhere (the document is blurred)
if (relatedTarget == null) {
// Update internal representation
this.__doWindowBlur();
// Reset active and focus
this.resetFocus();
this.resetActive();
}
},
webkit: qx.core.Environment.select("browser.name", {
// fix for [ISSUE #9174]
// distinguish bettween MS Edge, which is reported
// as engine webkit and all other webkit browsers
edge(domEvent) {
var relatedTarget = qx.bom.Event.getRelatedTarget(domEvent);
// If the focus goes to nowhere (the document is blurred)
if (relatedTarget == null) {
// Update internal representation
this.__doWindowBlur();
// Reset active and focus
this.resetFocus();
this.resetActive();
}
},
default(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target === this.getFocus()) {
this.resetFocus();
}
if (target === this.getActive()) {
this.resetActive();
}
}
}),
opera(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target == this._document) {
this.__doWindowBlur();
// Store old focus/active elements
// Opera do not fire focus events for them
// when refocussing the window (in my opinion an error)
this.__previousFocus = this.getFocus();
this.__previousActive = this.getActive();
this.resetFocus();
this.resetActive();
} else {
if (target === this.getFocus()) {
this.resetFocus();
}
if (target === this.getActive()) {
this.resetActive();
}
}
},
default: null
})
),
/**
* Native event listener for <code>blur</code>.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeBlur: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
gecko(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target === this._window || target === this._document) {
this.__doWindowBlur();
this.resetActive();
this.resetFocus();
}
},
webkit(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target === this._window || target === this._document) {
this.__doWindowBlur();
// Store old focus/active elements
// Opera do not fire focus events for them
// when refocussing the window (in my opinion an error)
this.__previousFocus = this.getFocus();
this.__previousActive = this.getActive();
this.resetActive();
this.resetFocus();
}
},
default: null
})
),
/**
* Native event listener for <code>focus</code>.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeFocus: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
gecko(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target === this._window || target === this._document) {
this.__doWindowFocus();
// Always speak of the body, not the window or document
target = this._body;
}
this.setFocus(target);
this.tryActivate(target);
},
webkit(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target === this._window || target === this._document) {
this.__doWindowFocus();
if (this.__previousFocus) {
this.setFocus(this.__previousFocus);
delete this.__previousFocus;
}
if (this.__previousActive) {
this.setActive(this.__previousActive);
delete this.__previousActive;
}
} else {
this.__relatedTarget = domEvent.relatedTarget;
this.setFocus(target);
this.__relatedTarget = null;
this.tryActivate(target);
}
},
default: null
})
),
/**
* Native event listener for <code>mousedown</code>.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeMouseDown: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
// Stop events when no focus element available (or blocked)
var focusTarget = this.__findFocusableElement(target);
if (focusTarget) {
// Add unselectable to keep selection
if (!this.__isSelectable(target)) {
// The element is not selectable. Block selection.
target.unselectable = "on";
// Unselectable may keep the current selection which
// is not what we like when changing the focus element.
// So we clear it
try {
if (document.selection) {
document.selection.empty();
}
} catch (ex) {
// ignore 'Unknown runtime error'
}
// The unselectable attribute stops focussing as well.
// Do this manually.
try {
focusTarget.focus();
} catch (ex) {
// ignore "Can't move focus of this control" error
}
}
} else {
// Stop event for blocking support
qx.bom.Event.preventDefault(domEvent);
// Add unselectable to keep selection
if (!this.__isSelectable(target)) {
target.unselectable = "on";
}
}
},
webkit(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
var focusTarget = this.__findFocusableElement(target);
if (focusTarget) {
this.setFocus(focusTarget);
} else {
qx.bom.Event.preventDefault(domEvent);
}
},
gecko(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
var focusTarget = this.__findFocusableElement(target);
if (focusTarget) {
this.setFocus(focusTarget);
} else {
qx.bom.Event.preventDefault(domEvent);
}
},
opera(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
var focusTarget = this.__findFocusableElement(target);
if (!this.__isSelectable(target)) {
// Prevent the default action for all non-selectable
// targets. This prevents text selection and context menu.
qx.bom.Event.preventDefault(domEvent);
// The stopped event keeps the selection
// of the previously focused element.
// We need to clear the old selection.
if (focusTarget) {
var current = this.getFocus();
if (current && current.selectionEnd) {
current.selectionStart = 0;
current.selectionEnd = 0;
current.blur();
}
// The prevented event also stop the focus, do
// it manually if needed.
if (focusTarget) {
this.setFocus(focusTarget);
}
}
} else if (focusTarget) {
this.setFocus(focusTarget);
}
},
default: null
})
),
/**
* Native event listener for <code>mouseup</code>.
*
* @signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeMouseUp: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (target.unselectable) {
target.unselectable = "off";
}
this.tryActivate(this.__fixFocus(target));
},
gecko(domEvent) {
// As of Firefox 3.0:
// Gecko fires mouseup on XUL elements
// We only want to deal with real HTML elements
var target = qx.bom.Event.getTarget(domEvent);
while (target && target.offsetWidth === undefined) {
target = target.parentNode;
}
if (target) {
this.tryActivate(target);
}
},
webkit(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
this.tryActivate(this.__fixFocus(target));
},
opera(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
this.tryActivate(this.__fixFocus(target));
},
default: null
})
),
/**
* Fix for bug #9331.
*
* @signature function(target)
* @param target {Element} element to check
* @return {Element} return correct target (in case of compound input controls should always return textfield);
*/
__getCorrectFocusTarget(target) {
var focusedElement = this.getFocus();
if (focusedElement && target != focusedElement) {
if (
focusedElement.nodeName.toLowerCase() === "input" ||
focusedElement.nodeName.toLowerCase() === "textarea"
) {
return focusedElement;
}
if (qx.Class.isClass("qx.ui.core.Widget")) {
// Check compound widgets
var widget = qx.ui.core.Widget.getWidgetByElement(focusedElement),
textField =
widget &&
widget.getChildControl &&
widget.getChildControl("textfield", true);
}
if (textField) {
return textField.getContentElement().getDomElement();
}
}
return target;
},
/**
* Fix for bug #2602.
*
* @signature function(target)
* @param target {Element} target element from mouse up event
* @return {Element} Element to activate;
*/
__fixFocus: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(target) {
return this.__getCorrectFocusTarget(target);
},
webkit(target) {
return this.__getCorrectFocusTarget(target);
},
default(target) {
return target;
}
})
),
/**
* Native event listener for <code>selectstart</code>.
*
*@signature function(domEvent)
* @param domEvent {Event} Native event
*/
__onNativeSelectStart: qx.event.GlobalError.observeMethod(
qx.core.Environment.select("engine.name", {
mshtml(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (!this.__isSelectable(target)) {
qx.bom.Event.preventDefault(domEvent);
}
},
webkit(domEvent) {
var target = qx.bom.Event.getTarget(domEvent);
if (!this.__isSelectable(target)) {
qx.bom.Event.preventDefault(domEvent);
}
},
default: null
})
),
/*
---------------------------------------------------------------------------
HELPER METHODS
---------------------------------------------------------------------------
*/
/**
* Whether the given element is focusable. This is perfectly modeled to the
* browsers behavior and this way may differ in the various clients.
*
* @param el {Element} DOM Element to query
* @return {Boolean} Whether the element is focusable
*/
__isFocusable(el) {
var index = qx.bom.element.Attribute.get(el, "tabIndex");
if (index >= 1) {
return true;
}
var focusable = qx.event.handler.Focus.FOCUSABLE_ELEMENTS;
if (index >= 0 && focusable[el.tagName]) {
return true;
}
return false;
},
/**
* Returns the next focusable parent element of an activated DOM element.
*
* @param el {Element} Element to start lookup with.
* @return {Element|null} The next focusable element.
*/
__findFocusableElement(el) {
while (el && el.nodeType === 1) {
if (el.getAttribute("qxKeepFocus") == "on") {
return null;
}
if (this.__isFocusable(el)) {
return el;
}
el = el.parentNode;
}
// This should be identical to the one which is selected when
// clicking into an empty page area. In mshtml this must be
// the body of the document.
return this._body;
},
/**
* Returns the next activatable element. May be the element itself.
* Works a bit different than the method {@link #__findFocusableElement}
* as it looks up for a parent which is has a keep focus flag. When
* there is such a parent it returns null otherwise the original
* incoming element.
*
* @param el {Element} Element to start lookup with.
* @return {Element} The next activatable element.
*/
__findActivatableElement(el) {
var orig = el;
while (el && el.nodeType === 1) {
if (el.getAttribute("qxKeepActive") == "on") {
return null;
}
el = el.parentNode;
}
return orig;
},
/**
* Whether the given el (or its content) should be selectable
* by the user.
*
* @param node {Element} Node to start lookup with
* @return {Boolean} Whether the content is selectable.
*/
__isSelectable(node) {
while (node && node.nodeType === 1) {
var attr = node.getAttribute("qxSelectable");
if (attr != null) {
return attr === "on";
}
node = node.parentNode;
}
return true;
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// apply routine
_applyActive(value, old) {
// Fire events
if (old) {
this.__fireEvent(old, value, "deactivate", true);
}
if (value) {
this.__fireEvent(value, old, "activate", true);
}
// correct scroll position for iOS 7 to 14 [ISSUE #9393 and #10565]
if (this.__needsScrollFix) {
window.scrollTo(0, 0);
}
},
// apply routine
_applyFocus(value, old) {
// Fire bubbling events
if (old) {
this.__fireEvent(old, value, "focusout", true);
}
if (value) {
this.__fireEvent(value, old, "focusin", true);
}
// Fire after events
if (old) {
this.__fireEvent(old, value, "blur", false);
}
if (value) {
this.__fireEvent(value, old || this.__relatedTarget, "focus", false);
}
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
this._stopObserver();
this._manager =
this._window =
this._document =
this._root =
this._body =
this.__mouseActive =
this.__relatedTarget =
null;
},
/*
*****************************************************************************
DEFER
*****************************************************************************
*/
defer(statics) {
qx.event.Registration.addHandler(statics);
// For faster lookups generate uppercase tag names dynamically
var focusable = statics.FOCUSABLE_ELEMENTS;
for (var entry in focusable) {
focusable[entry.toUpperCase()] = 1;
}
}
});