@qooxdoo/framework
Version:
The JS Framework for Coders
646 lines (559 loc) • 17.6 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-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)
* Andreas Ecker (ecker)
* Fabian Jakobs (fjakobs)
* Christian Hagendorn (chris_schmidt)
************************************************************************ */
/**
* This class provides an unified mouse event handler for Internet Explorer,
* Firefox, Opera and Safari
*
* NOTE: Instances of this class must be disposed of after use
*
* @require(qx.event.handler.UserAction)
* @ignore(qx.event.handler.DragDrop)
* @ignore(performance.now)
*/
qx.Class.define("qx.event.handler.Mouse", {
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
*/
construct(manager) {
super();
// Define shorthands
this.__manager = manager;
this.__window = manager.getWindow();
this.__root = this.__window.document;
this.__onNativeListener = qx.lang.Function.listener(this._onNative, this);
// Initialize observers
this._initButtonObserver();
this._initMoveObserver();
this._initWheelObserver();
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics: {
/** @type {Integer} Priority of this handler */
PRIORITY: qx.event.Registration.PRIORITY_NORMAL,
/** @type {Map} Supported event types */
SUPPORTED_TYPES: {
auxclick: 1,
click: 1,
contextmenu: 1,
dblclick: 1,
mousedown: 1,
mouseenter: 1,
mouseleave: 1,
mousemove: 1,
mouseout: 1,
mouseover: 1,
mouseup: 1,
mousewheel: 1
},
/** @type{Map} these event types cannot be attached to the root (the document), they must be attached to the element itself */
NON_BUBBLING_EVENTS: {
mouseenter: true,
mouseleave: true
},
/** @type {Integer} Which target check to use */
TARGET_CHECK:
qx.event.IEventHandler.TARGET_DOMNODE +
qx.event.IEventHandler.TARGET_DOCUMENT +
qx.event.IEventHandler.TARGET_WINDOW,
/** @type {Integer} Whether the method "canHandleEvent" must be called */
IGNORE_CAN_HANDLE: true
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
__onButtonEventWrapper: null,
__onMoveEventWrapper: null,
__onWheelEventWrapper: null,
__lastEventType: null,
__lastMouseDownTarget: null,
__manager: null,
__window: null,
__root: null,
__preventNextClick: null,
/** @type{Function} wrapper for `_onNative`, bound as a native listener */
__onNativeListener: null,
/*
---------------------------------------------------------------------------
EVENT HANDLER INTERFACE
---------------------------------------------------------------------------
*/
// interface implementation
canHandleEvent(target, type) {},
/**
* @Override
*/
registerEvent(target, type, capture) {
if (qx.event.handler.Mouse.NON_BUBBLING_EVENTS[type]) {
qx.bom.Event.addNativeListener(target, type, this.__onNativeListener);
} else if (qx.core.Environment.get("os.name") === "ios") {
// The iPhone requires for attaching mouse events natively to every element which
// should react on mouse events. As of version 3.0 it also requires to keep the
// listeners as long as the event should work. In 2.0 it was enough to attach the
// listener once.
target["on" + type] = function () {
return null;
};
}
},
/**
* @Override
*/
unregisterEvent(target, type, capture) {
if (qx.event.handler.Mouse.NON_BUBBLING_EVENTS[type]) {
qx.bom.Event.removeNativeListener(
target,
type,
this.__onNativeListener
);
} else if (qx.core.Environment.get("os.name") === "ios") {
target["on" + type] = undefined;
}
},
/**
* Default event handler for events that do not bubble
*
* @signature function(domEvent, eventId)
* @param domEvent {Event} Native event
*/
_onNative: qx.event.GlobalError.observeMethod(function (domEvent) {
let target = qx.bom.Event.getTarget(domEvent);
qx.event.Registration.fireNonBubblingEvent(
target,
domEvent.type,
qx.event.type.Mouse,
[domEvent, target, undefined, undefined, domEvent.cancelable]
);
}),
/*
---------------------------------------------------------------------------
HELPER
---------------------------------------------------------------------------
*/
/**
* Fire a mouse event with the given parameters
*
* @param domEvent {Event} DOM event
* @param type {String} type of the event
* @param target {Element} event target
*/
__fireEvent(domEvent, type, target) {
if (!target) {
target = qx.bom.Event.getTarget(domEvent);
}
// we need a true node for the fireEvent
// e.g. when hovering over text of disabled textfields IE is returning
// an empty object as "srcElement"
if (target && target.nodeType) {
qx.event.Registration.fireEvent(
target,
type || domEvent.type,
type == "mousewheel" ? qx.event.type.MouseWheel : qx.event.type.Mouse,
[domEvent, target, null, true, true]
);
}
// Fire user action event
qx.event.Registration.fireEvent(
this.__window,
"useraction",
qx.event.type.Data,
[type || domEvent.type]
);
},
/**
* Helper to prevent the next click.
* @internal
*/
preventNextClick() {
this.__preventNextClick = true;
},
/*
---------------------------------------------------------------------------
OBSERVER INIT
---------------------------------------------------------------------------
*/
/**
* Initializes the native mouse button event listeners.
*
* @signature function()
*/
_initButtonObserver() {
this.__onButtonEventWrapper = qx.lang.Function.listener(
this._onButtonEvent,
this
);
var Event = qx.bom.Event;
Event.addNativeListener(
this.__root,
"mousedown",
this.__onButtonEventWrapper
);
Event.addNativeListener(
this.__root,
"mouseup",
this.__onButtonEventWrapper
);
Event.addNativeListener(
this.__root,
"click",
this.__onButtonEventWrapper
);
Event.addNativeListener(
this.__root,
"auxclick",
this.__onButtonEventWrapper
);
Event.addNativeListener(
this.__root,
"dblclick",
this.__onButtonEventWrapper
);
Event.addNativeListener(
this.__root,
"contextmenu",
this.__onButtonEventWrapper
);
},
/**
* Initializes the native mouse move event listeners.
*
* @signature function()
*/
_initMoveObserver() {
this.__onMoveEventWrapper = qx.lang.Function.listener(
this._onMoveEvent,
this
);
var Event = qx.bom.Event;
Event.addNativeListener(
this.__root,
"mousemove",
this.__onMoveEventWrapper
);
Event.addNativeListener(
this.__root,
"mouseout",
this.__onMoveEventWrapper
);
Event.addNativeListener(
this.__root,
"mouseover",
this.__onMoveEventWrapper
);
},
/**
* Initializes the native mouse wheel event listeners.
*
* @signature function()
*/
_initWheelObserver() {
this.__onWheelEventWrapper = qx.lang.Function.listener(
this._onWheelEvent,
this
);
var data = qx.bom.client.Event.getMouseWheel(this.__window);
qx.bom.Event.addNativeListener(
data.target,
data.type,
this.__onWheelEventWrapper
);
},
/*
---------------------------------------------------------------------------
OBSERVER STOP
---------------------------------------------------------------------------
*/
/**
* Disconnects the native mouse button event listeners.
*
* @signature function()
*/
_stopButtonObserver() {
var Event = qx.bom.Event;
Event.removeNativeListener(
this.__root,
"mousedown",
this.__onButtonEventWrapper
);
Event.removeNativeListener(
this.__root,
"mouseup",
this.__onButtonEventWrapper
);
Event.removeNativeListener(
this.__root,
"click",
this.__onButtonEventWrapper
);
Event.removeNativeListener(
this.__root,
"dblclick",
this.__onButtonEventWrapper
);
Event.removeNativeListener(
this.__root,
"contextmenu",
this.__onButtonEventWrapper
);
},
/**
* Disconnects the native mouse move event listeners.
*
* @signature function()
*/
_stopMoveObserver() {
var Event = qx.bom.Event;
Event.removeNativeListener(
this.__root,
"mousemove",
this.__onMoveEventWrapper
);
Event.removeNativeListener(
this.__root,
"mouseover",
this.__onMoveEventWrapper
);
Event.removeNativeListener(
this.__root,
"mouseout",
this.__onMoveEventWrapper
);
},
/**
* Disconnects the native mouse wheel event listeners.
*
* @signature function()
*/
_stopWheelObserver() {
var data = qx.bom.client.Event.getMouseWheel(this.__window);
qx.bom.Event.removeNativeListener(
data.target,
data.type,
this.__onWheelEventWrapper
);
},
/*
---------------------------------------------------------------------------
NATIVE EVENT OBSERVERS
---------------------------------------------------------------------------
*/
/**
* Global handler for all mouse move related events like "mousemove",
* "mouseout" and "mouseover".
*
* @signature function(domEvent)
* @param domEvent {Event} DOM event
*/
_onMoveEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
this.__fireEvent(domEvent);
}),
/**
* Global handler for all mouse button related events like "mouseup",
* "mousedown", "click", "dblclick" and "contextmenu".
*
* @signature function(domEvent)
* @param domEvent {Event} DOM event
*/
_onButtonEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
var type = domEvent.type;
var target = qx.bom.Event.getTarget(domEvent);
if (type == "click" && this.__preventNextClick) {
delete this.__preventNextClick;
return;
}
// Safari (and maybe gecko) takes text nodes as targets for events
// See: http://www.quirksmode.org/js/events_properties.html
if (
qx.core.Environment.get("engine.name") == "gecko" ||
qx.core.Environment.get("engine.name") == "webkit"
) {
if (target && target.nodeType == 3) {
target = target.parentNode;
}
}
// prevent click events on drop during Drag&Drop [BUG #6846]
var isDrag =
qx.event.handler.DragDrop &&
this.__manager.getHandler(qx.event.handler.DragDrop).isSessionActive();
if (isDrag && type == "click") {
return;
}
if (this.__doubleClickFixPre) {
this.__doubleClickFixPre(domEvent, type, target);
}
this.__fireEvent(domEvent, type, target);
/*
* In order to normalize middle button click events we
* need to fire an artificial click event if the client
* fires auxclick events for non primary buttons instead.
*
* See https://github.com/qooxdoo/qooxdoo/issues/9268
*/
if (type == "auxclick" && domEvent.button == 1) {
this.__fireEvent(domEvent, "click", target);
}
if (this.__rightClickFixPost) {
this.__rightClickFixPost(domEvent, type, target);
}
if (this.__differentTargetClickFixPost && !isDrag) {
this.__differentTargetClickFixPost(domEvent, type, target);
}
this.__lastEventType = type;
}),
/**
* Global handler for the mouse wheel event.
*
* @signature function(domEvent)
* @param domEvent {Event} DOM event
*/
_onWheelEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
this.__fireEvent(domEvent, "mousewheel");
}),
/*
---------------------------------------------------------------------------
CROSS BROWSER SUPPORT FIXES
---------------------------------------------------------------------------
*/
/**
* Normalizes the click sequence of right click events in Webkit and Opera.
* The normalized sequence is:
*
* 1. mousedown <- not fired by Webkit
* 2. mouseup <- not fired by Webkit
* 3. contextmenu <- not fired by Opera
*
* @param domEvent {Event} original DOM event
* @param type {String} event type
* @param target {Element} event target of the DOM event.
*
* @signature function(domEvent, type, target)
*/
__rightClickFixPost: qx.core.Environment.select("engine.name", {
opera(domEvent, type, target) {
if (type == "mouseup" && domEvent.button == 2) {
this.__fireEvent(domEvent, "contextmenu", target);
}
},
default: null
}),
/**
* Normalizes the click sequence of double click event in the Internet
* Explorer. The normalized sequence is:
*
* 1. mousedown
* 2. mouseup
* 3. click
* 4. mousedown <- not fired by IE
* 5. mouseup
* 6. click <- not fired by IE
* 7. dblclick
*
* Note: This fix is only applied, when the IE event model is used, otherwise
* the fix is ignored.
*
* @param domEvent {Event} original DOM event
* @param type {String} event type
* @param target {Element} event target of the DOM event.
*
* @signature function(domEvent, type, target)
*/
__doubleClickFixPre: qx.core.Environment.select("engine.name", {
mshtml(domEvent, type, target) {
// Do only apply the fix when the event is from the IE event model,
// otherwise do not apply the fix.
if (domEvent.target !== undefined) {
return;
}
if (type == "mouseup" && this.__lastEventType == "click") {
this.__fireEvent(domEvent, "mousedown", target);
} else if (type == "dblclick") {
this.__fireEvent(domEvent, "click", target);
}
},
default: null
}),
/**
* If the mouseup event happens on a different target than the corresponding
* mousedown event the internet explorer dispatches a click event on the
* first common ancestor of both targets. The presence of this click event
* is essential for the qooxdoo widget system. All other browsers don't fire
* the click event so it must be emulated.
*
* @param domEvent {Event} original DOM event
* @param type {String} event type
* @param target {Element} event target of the DOM event.
*
* @signature function(domEvent, type, target)
*/
__differentTargetClickFixPost: qx.core.Environment.select("engine.name", {
mshtml: null,
default(domEvent, type, target) {
switch (type) {
case "mousedown":
this.__lastMouseDownTarget = target;
break;
case "mouseup":
if (target !== this.__lastMouseDownTarget) {
var commonParent = qx.dom.Hierarchy.getCommonParent(
target,
this.__lastMouseDownTarget
);
if (commonParent) {
this.__fireEvent(domEvent, "click", commonParent);
}
}
}
}
})
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
this._stopButtonObserver();
this._stopMoveObserver();
this._stopWheelObserver();
this.__manager =
this.__window =
this.__root =
this.__lastMouseDownTarget =
null;
},
/*
*****************************************************************************
DEFER
*****************************************************************************
*/
defer(statics) {
qx.event.Registration.addHandler(statics);
}
});