@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
439 lines (381 loc) • 17.7 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
'sap/base/util/Version',
'sap/ui/core/Element',
'sap/ui/events/PseudoEvents',
'sap/ui/events/checkMouseEnterOrLeave',
'sap/ui/events/ControlEvents',
'sap/ui/Device',
'sap/ui/events/TouchToMouseMapping',
'sap/ui/thirdparty/jquery',
'sap/ui/thirdparty/jquery-mobile-custom'
], function(Version, Element, PseudoEvents, checkMouseEnterOrLeave, ControlEvents, Device, TouchToMouseMapping, jQuery /*, jQueryMobile*/) {
"use strict";
/**
* @namespace
* @alias module:sap/ui/events/jquery/EventSimulation
* @private
* @ui5-restricted sap.ui.core
*/
var oEventSimulation = {};
var jQVersion = Version(jQuery.fn.jquery);
oEventSimulation.aAdditionalControlEvents = [];
oEventSimulation.aAdditionalPseudoEvents = [];
/**
* This function adds the simulated event prefixed with string "sap" to ControlEvents.events.
*
* When UIArea binds to the simulated event with prefix, it internally binds to the original events with the given handler and
* also provides the additional configuration data in the following format:
*
* {
* domRef: // the DOM reference of the UIArea
* eventName: // the simulated event name
* sapEventName: // the simulated event name with sap prefix
* eventHandle: // the handler that should be registered to simulated event with sap prefix
* }
*
* @param {string} sSimEventName The name of the simulated event
* @param {array} aOrigEvents The array of original events that should be simulated from
* @param {function} fnHandler The function which is bound to the original events
* @private
*/
oEventSimulation._createSimulatedEvent = function(sSimEventName, aOrigEvents, fnHandler) {
var sHandlerKey = "__" + sSimEventName + "Handler";
var sSapSimEventName = "sap" + sSimEventName;
this.aAdditionalControlEvents.push(sSapSimEventName);
this.aAdditionalPseudoEvents.push({
sName: sSimEventName,
aTypes: [sSapSimEventName],
fnCheck: function(oEvent) {
return true;
}
});
jQuery.event.special[sSapSimEventName] = {
// When binding to the simulated event with prefix is done through jQuery, this function is called and redirect the registration
// to the original events. Doing in this way we can simulate the event from listening to the original events.
add: function(oHandle) {
var that = this,
$this = jQuery(this),
oAdditionalConfig = {
domRef: that,
eventName: sSimEventName,
sapEventName: sSapSimEventName,
eventHandle: oHandle
};
var fnHandlerWrapper = function(oEvent) {
fnHandler(oEvent, oAdditionalConfig);
};
oHandle.__sapSimulatedEventHandler = fnHandlerWrapper;
for (var i = 0; i < aOrigEvents.length; i++) {
$this.on(aOrigEvents[i], fnHandlerWrapper);
}
},
// When unbinding to the simulated event with prefix is done through jQuery, this function is called and redirect the deregistration
// to the original events.
remove: function(oHandle) {
var $this = jQuery(this);
var fnHandler = oHandle.__sapSimulatedEventHandler;
$this.removeData(sHandlerKey + oHandle.guid);
for (var i = 0; i < aOrigEvents.length; i++) {
jQuery.event.remove(this, aOrigEvents[i], fnHandler);
}
}
};
};
/**
* This function simulates the corresponding touch event by listening to mouse event.
*
* The simulated event will be dispatch through UI5 event delegation which means that the <code>on"EventName"</code> function is called
* on control's prototype.
*
* @param {jQuery.Event} oEvent The original event object
* @param {object} oConfig Additional configuration passed from createSimulatedEvent function
* @private
*/
oEventSimulation._handleMouseToTouchEvent = function(oEvent, oConfig) {
// Suppress the delayed mouse events simulated on touch enabled device
// the mark is done within jquery-mobile-custom.js
if (oEvent.isMarked("delayedMouseEvent")) {
return;
}
var $DomRef = jQuery(oConfig.domRef),
oControl = Element.closestTo(oEvent.target),
sTouchStartControlId = $DomRef.data("__touchstart_control"),
oTouchStartControlDOM = sTouchStartControlId && window.document.getElementById(sTouchStartControlId);
// Checks if the mouseout event should be handled, the mouseout of the inner DOM shouldn't be handled when the mouse cursor
// is still inside the control's root DOM node
if (oEvent.type === "mouseout" && !checkMouseEnterOrLeave(oEvent, oConfig.domRef)
&& (!oTouchStartControlDOM || !checkMouseEnterOrLeave(oEvent, oTouchStartControlDOM))
) {
return;
}
var oNewEvent = jQuery.event.fix(oEvent.originalEvent || oEvent);
oNewEvent.type = oConfig.sapEventName;
//reset the _sapui_handledByUIArea flag
if (oNewEvent.isMarked("firstUIArea")) {
oNewEvent.setMark("handledByUIArea", false);
}
var aTouches = [{
identifier: 1,
pageX: oNewEvent.pageX,
pageY: oNewEvent.pageY,
clientX: oNewEvent.clientX,
clientY: oNewEvent.clientY,
screenX: oNewEvent.screenX,
screenY: oNewEvent.screenY,
target: oNewEvent.target,
radiusX: 1,
radiusY: 1,
rotationAngle: 0
}];
switch (oConfig.eventName) {
case "touchstart":
// save the control id in case of touchstart event
if (oControl) {
$DomRef.data("__touchstart_control", oControl.getId());
}
// fall through
case "touchmove":
oNewEvent.touches = oNewEvent.changedTouches = oNewEvent.targetTouches = aTouches;
break;
case "touchend":
oNewEvent.changedTouches = aTouches;
oNewEvent.touches = oNewEvent.targetTouches = [];
break;
// no default
}
if (oConfig.eventName === "touchstart" || $DomRef.data("__touch_in_progress")) {
$DomRef.data("__touch_in_progress", "X");
// When saptouchend event is generated from mouseout event, it has to be marked for being correctly handled inside UIArea.
// for example, when sap.m.Image control is used inside sap.m.Button control, the following situation can happen:
// 1. Mousedown on image.
// 2. Keep mousedown and move mouse out of image.
// 3. ontouchend function will be called on image control and bubbled up to button control
// 4. However, the ontouchend function shouldn't be called on button.
//
// With this parameter, UIArea can check if the touchend is generated from mouseout event and check if the target is still
// inside the current target. Executing the corresponding logic only when the target is out of the current target.
if (oEvent.type === "mouseout") {
oNewEvent.setMarked("fromMouseout");
}
// touchstart event is always forwarded to the control without any check
// other events are checked with the touchstart control id in UIArea.js and we save the touchstart control
// id to the event. In UIArea, the event is dispatched to a UI5 element only when the root DOM of that UI5
// element contains or equals the touchstart control DOM
if (oConfig.eventName !== "touchstart" && (!oControl || oControl.getId() !== sTouchStartControlId)) {
oNewEvent.setMark("scopeCheckId", sTouchStartControlId);
}
// dragstart event is only used to determine when to stop the touch process and shouldn't trigger any event
if (oEvent.type !== "dragstart") {
oConfig.eventHandle.handler.call(oConfig.domRef, oNewEvent);
}
// here the fromMouseout flag is checked, terminate the touch progress when the native event is dragstart or touchend event
// is not marked with fromMouseout.
if ((oConfig.eventName === "touchend" || oEvent.type === "dragstart") && !oNewEvent.isMarked("fromMouseout")) {
$DomRef.removeData("__touch_in_progress");
$DomRef.removeData("__touchstart_control");
}
}
};
// Simulate touch events on NOT delayed mouse events (delayed mouse
// events are filtered out in fnMouseToTouchHandler)
oEventSimulation._initTouchEventSimulation = function() {
this._createSimulatedEvent("touchstart", ["mousedown"], this._handleMouseToTouchEvent);
this._createSimulatedEvent("touchend", ["mouseup", "mouseout"], this._handleMouseToTouchEvent);
// Browser doesn't fire any mouse event after dragstart, so we need to listen to dragstart to cancel the current touch process in order
// to correctly stop firing the touchmove event
this._createSimulatedEvent("touchmove", ["mousemove", "dragstart"], this._handleMouseToTouchEvent);
};
// polyfill for iOS context menu event (mapped to taphold)
oEventSimulation._initContextMenuSimulation = function() {
//map the taphold event to contextmenu event
var fnSimulatedFunction = function(oEvent, oConfig) {
var oNewEvent = jQuery.event.fix(oEvent.originalEvent || oEvent);
oNewEvent.type = oConfig.sapEventName;
// The original handler is called only when there's no text selected
if (!window.getSelection || !window.getSelection() || window.getSelection().toString() === "") {
oConfig.eventHandle.handler.call(oConfig.domRef, oNewEvent);
}
};
this._createSimulatedEvent("contextmenu", ["taphold"], fnSimulatedFunction);
};
// Simulate mouse events on browsers firing touch events
oEventSimulation._initMouseEventSimulation = function() {
var bFingerIsMoved = false,
iMoveThreshold = jQuery.vmouse.moveDistanceThreshold,
iStartX, iStartY,
iOffsetX, iOffsetY;
var fnCreateNewEvent = function(oEvent, oConfig, oMappedEvent) {
var oNewEvent = jQuery.event.fix(oEvent.originalEvent || oEvent);
oNewEvent.type = oConfig.sapEventName;
delete oNewEvent.touches;
delete oNewEvent.changedTouches;
delete oNewEvent.targetTouches;
//TODO: add other properties that should be copied to the new event
oNewEvent.screenX = oMappedEvent.screenX;
oNewEvent.screenY = oMappedEvent.screenY;
oNewEvent.clientX = oMappedEvent.clientX;
oNewEvent.clientY = oMappedEvent.clientY;
oNewEvent.ctrlKey = oMappedEvent.ctrlKey;
oNewEvent.altKey = oMappedEvent.altKey;
oNewEvent.shiftKey = oMappedEvent.shiftKey;
// The simulated mouse event should always be clicked by the left key of the mouse
oNewEvent.button = 0;
return oNewEvent;
};
/**
* This function simulates the corresponding mouse event by listening to touch event (touchmove).
*
* The simulated event will be dispatch through UI5 event delegation which means that the on"EventName" function is called
* on control's prototype.
*
* @param {jQuery.Event} oEvent The original event object
* @param {object} oConfig Additional configuration passed from createSimulatedEvent function
*/
var fnTouchMoveToMouseHandler = function(oEvent, oConfig) {
if (oEvent.isMarked("handledByTouchToMouse")) {
return;
}
oEvent.setMarked("handledByTouchToMouse");
if (!bFingerIsMoved) {
var oTouch = oEvent.originalEvent.touches[0];
bFingerIsMoved = (Math.abs(oTouch.pageX - iStartX) > iMoveThreshold ||
Math.abs(oTouch.pageY - iStartY) > iMoveThreshold);
}
var oNewEvent = fnCreateNewEvent(oEvent, oConfig, oEvent.touches[0]);
setTimeout(function() {
oNewEvent.setMark("handledByUIArea", false);
oConfig.eventHandle.handler.call(oConfig.domRef, oNewEvent);
}, 0);
};
/**
* This function simulates the corresponding mouse event by listening to touch event (touchstart, touchend, touchcancel).
*
* The simulated event will be dispatch through UI5 event delegation which means that the on"EventName" function is called
* on control's prototype.
*
* @param {jQuery.Event} oEvent The original event object
* @param {object} oConfig Additional configuration passed from createSimulatedEvent function
*/
var fnTouchToMouseHandler = function(oEvent, oConfig) {
if (oEvent.isMarked("handledByTouchToMouse")) {
return;
}
oEvent.setMarked("handledByTouchToMouse");
var oNewStartEvent, oNewEndEvent, bSimulateClick;
function createNewEvent() {
return fnCreateNewEvent(oEvent, oConfig, oConfig.eventName === "mouseup" ? oEvent.changedTouches[0] : oEvent.touches[0]);
}
if (oEvent.type === "touchstart") {
var oTouch = oEvent.originalEvent.touches[0];
bFingerIsMoved = false;
iStartX = oTouch.pageX;
iStartY = oTouch.pageY;
iOffsetX = Math.round(oTouch.pageX - jQuery(oEvent.target).offset().left);
iOffsetY = Math.round(oTouch.pageY - jQuery(oEvent.target).offset().top);
oNewStartEvent = createNewEvent();
setTimeout(function() {
oNewStartEvent.setMark("handledByUIArea", false);
oConfig.eventHandle.handler.call(oConfig.domRef, oNewStartEvent);
}, 0);
} else if (oEvent.type === "touchend") {
oNewEndEvent = createNewEvent();
bSimulateClick = !bFingerIsMoved;
setTimeout(function() {
oNewEndEvent.setMark("handledByUIArea", false);
oConfig.eventHandle.handler.call(oConfig.domRef, oNewEndEvent);
if (bSimulateClick) {
// also call the onclick event handler when touchend event is received and the movement is within threshold
oNewEndEvent.type = "click";
oNewEndEvent.getPseudoTypes = jQuery.Event.prototype.getPseudoTypes; //Reset the pseudo types due to type change
oNewEndEvent.setMark("handledByUIArea", false);
oNewEndEvent.offsetX = iOffsetX; // use offset from touchstart
oNewEndEvent.offsetY = iOffsetY; // use offset from touchstart
oConfig.eventHandle.handler.call(oConfig.domRef, oNewEndEvent);
}
}, 0);
}
};
this._createSimulatedEvent("mousedown", ["touchstart"], fnTouchToMouseHandler);
this._createSimulatedEvent("mousemove", ["touchmove"], fnTouchMoveToMouseHandler);
this._createSimulatedEvent("mouseup", ["touchend", "touchcancel"], fnTouchToMouseHandler);
};
oEventSimulation._init = function(aEvents) {
// Define additional jQuery Mobile events to be added to the event list
// TODO taphold cannot be used (does not bubble / has no target property) -> Maybe provide own solution
// IMPORTANT: update the public documentation when extending this list
this.aAdditionalControlEvents.push("swipe", "tap", "swipeleft", "swiperight", "scrollstart", "scrollstop");
//Define additional pseudo events to be added to the event list
this.aAdditionalPseudoEvents.push({
sName: "swipebegin", aTypes: ["swipeleft", "swiperight"], fnCheck: function(oEvent) {
var bRtl = sap.ui.getCore().getConfiguration().getRTL();
return (bRtl && oEvent.type === "swiperight") || (!bRtl && oEvent.type === "swipeleft");
}
});
this.aAdditionalPseudoEvents.push({
sName: "swipeend", aTypes: ["swipeleft", "swiperight"], fnCheck: function(oEvent) {
var bRtl = sap.ui.getCore().getConfiguration().getRTL();
return (!bRtl && oEvent.type === "swiperight") || (bRtl && oEvent.type === "swipeleft");
}
});
// Add all defined events to the event infrastructure
//
// jQuery has inversed the order of event registration when multiple events are passed into jQuery.on method from version 1.9.1.
//
// UIArea binds to both touchstart and saptouchstart event and saptouchstart internally also binds to touchstart event. Before
// jQuery version 1.9.1, the touchstart event handler is called before the saptouchstart event handler and our flags (e.g. _sapui_handledByUIArea)
// still work. However since the order of event registration is inversed from jQuery version 1.9.1, the saptouchstart event handler is called
// before the touchstart one, our flags don't work anymore.
//
// Therefore jQuery version needs to be checked in order to decide the event order in ControlEvents.events.
if (jQVersion.compareTo("1.9.1") < 0) {
aEvents = aEvents.concat(this.aAdditionalControlEvents);
} else {
aEvents = this.aAdditionalControlEvents.concat(aEvents);
}
for (var i = 0; i < this.aAdditionalPseudoEvents.length; i++) {
PseudoEvents.addEvent(this.aAdditionalPseudoEvents[i]);
}
return aEvents;
};
if (Device.browser.webkit && /Mobile/.test(navigator.userAgent) && Device.support.touch) {
TouchToMouseMapping.init(window.document);
oEventSimulation.disableTouchToMouseHandling = TouchToMouseMapping.disableTouchToMouseHandling;
}
if (!oEventSimulation.disableTouchToMouseHandling) {
oEventSimulation.disableTouchToMouseHandling = function() {};
}
// touch events natively supported
if (Device.support.touch) {
// Define additional native events to be added to the event list.
// TODO: maybe add "gesturestart", "gesturechange", "gestureend" later?
ControlEvents.events.push("touchstart", "touchend", "touchmove", "touchcancel");
}
//Add mobile touch events if touch is supported
(function initTouchEventSupport() {
oEventSimulation.touchEventMode = "SIM";
if (Device.support.touch) { // touch events natively supported
oEventSimulation.touchEventMode = "ON";
// ensure that "oEvent.touches", ... works (and not only "oEvent.originalEvent.touches", ...)
if (jQVersion.compareTo("3.0.0") < 0) {
jQuery.event.props.push("touches", "targetTouches", "changedTouches");
} // else: jQuery 3.0ff already manages these properties
}
oEventSimulation._initTouchEventSimulation();
// polyfill for iOS context menu event (mapped to taphold)
if (Device.os.ios) {
oEventSimulation._initContextMenuSimulation();
}
if (Device.support.touch) {
// Deregister the previous touch to mouse event simulation (see line 25 in this file)
oEventSimulation.disableTouchToMouseHandling();
oEventSimulation._initMouseEventSimulation();
}
ControlEvents.events = oEventSimulation._init(ControlEvents.events);
}());
return oEventSimulation;
});