@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
785 lines (660 loc) • 24 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.
*/
// Provides control sap.ui.core.ScrollBar.
sap.ui.define([
'sap/ui/Device',
'./Control',
'./library',
"./ScrollBarRenderer",
"sap/ui/performance/trace/Interaction",
"sap/base/Log",
"sap/ui/events/jquery/EventSimulation",
"sap/ui/thirdparty/jquery",
"sap/ui/core/Configuration"
],
function(
Device,
Control,
library,
ScrollBarRenderer,
Interaction,
Log,
EventSimulation,
jQuery,
Configuration
) {
"use strict";
// shortcut for enum(s)
var ScrollBarAction = library.ScrollBarAction;
/**
* Constructor for a new ScrollBar.
*
* @param {string} [sId] id for the new control, generated automatically if no id is given
* @param {object} [mSettings] initial settings for the new control
*
* @class
* The ScrollBar control can be used for virtual scrolling of a certain area.
* This means: to simulate a very large scrollable area when technically the area is small and the control takes care of displaying the respective part only. E.g. a Table control can take care of only rendering the currently visible rows and use this ScrollBar control to make the user think he actually scrolls through a long list.
* @extends sap.ui.core.Control
* @version 1.111.5
*
* @public
* @deprecated as of version 1.56
* @alias sap.ui.core.ScrollBar
*/
var ScrollBar = Control.extend("sap.ui.core.ScrollBar", /** @lends sap.ui.core.ScrollBar.prototype */ {
metadata : {
library : "sap.ui.core",
properties : {
/**
* Orientation. Defines if the Scrollbar is vertical or horizontal.
*/
vertical : {type : "boolean", group : "Behavior", defaultValue : true},
/**
* Scroll position in steps or pixels.
*/
scrollPosition : {type : "int", group : "Behavior", defaultValue : null},
/**
* Size of the Scrollbar (in pixels).
*/
size : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null},
/**
* Size of the scrollable content (in pixels).
*/
contentSize : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null},
/**
* Number of steps to scroll. Used if the size of the content is not known as the data is loaded dynamically.
*/
steps : {type : "int", group : "Dimension", defaultValue : null}
},
events : {
/**
* Scroll event.
*/
scroll : {
parameters : {
/**
* Actions are: Click on track, button, drag of thumb, or mouse wheel click.
*/
action : {type : "sap.ui.core.ScrollBarAction"},
/**
* Direction of scrolling: back (up) or forward (down).
*/
forward : {type : "boolean"},
/**
* Current Scroll position either in pixels or in steps.
*/
newScrollPos : {type : "int"},
/**
* Old Scroll position - can be in pixels or in steps.
*/
oldScrollPos : {type : "int"}
}
}
}
},
renderer: ScrollBarRenderer
});
// =============================================================================
// BASIC CONTROL API
// =============================================================================
/**
* Initialization of the Scrollbar control
* @private
*/
ScrollBar.prototype.init = function(){
// Set scope of event handlers to this
this.ontouchstart = this.ontouchstart.bind(this);
this.ontouchmove = this.ontouchmove.bind(this);
this.ontouchend = this.ontouchend.bind(this);
this.ontouchcancel = this.ontouchcancel.bind(this);
this.onmousewheel = this.onmousewheel.bind(this);
this.onscroll = this.onscroll.bind(this);
// jQuery Object - Dom reference of the scroll bar
this._$ScrollDomRef = null;
// In pixels - exact position
this._iOldScrollPos = 0;
// In steps
this._iOldStep = 0;
// True if the scroll position was verified. And false if the check was not done yet - for example if the rendering is not done completely
this._bScrollPosIsChecked = false;
// RTL mode
this._bRTL = Configuration.getRTL();
// suppress scroll event
this._bSuppressScroll = false;
this._iMaxContentDivSize = 1000000; // small value that all browsers still can render without any problems
if (EventSimulation.touchEventMode === "ON") {
sap.ui.requireSync("sap/ui/thirdparty/zyngascroll"); // legacy-relevant: sap.ui.core.ScrollBar is deprecated
// Remember last touch scroller position to prevent unneeded rendering
this._iLastTouchScrollerPosition = null;
// The threshold in pixel for a step when scrolled by touch events
this._iTouchStepTreshold = 24;
// Some zynga scroller methods call the touch handler. By settings this variable to false, touch handling is prevented and
// number of unneeded rendering is reduced.
this._bSkipTouchHandling = false;
this._oTouchScroller = new window.Scroller(this._handleTouchScroll.bind(this), {
bouncing:false
});
}
};
/**
* Rerendering handling
* @private
*/
ScrollBar.prototype.onBeforeRendering = function() {
this.$("sb").off("scroll", this.onscroll);
};
/**
* Rerendering handling
* @private
*/
ScrollBar.prototype.onAfterRendering = function () {
// count of steps (comes per API)
this._iSteps = this.getSteps();
// content size in pixel
var sContentSize = this.getContentSize();
// determine the mode
this._bStepMode = !sContentSize;
var iScrollBarSize = this.getSize();
if (iScrollBarSize.endsWith("px")) {
iScrollBarSize = iScrollBarSize.substr(0, iScrollBarSize.length - 2);
} else {
iScrollBarSize = this.getVertical() ? this.$().height() : this.$().width();
}
var stepSize = null;
var $ffsize = this.$("ffsize");
if (Device.browser.firefox) {
stepSize = $ffsize.outerHeight();
if ( stepSize === 0) {
// the following code is used if a container of the scrollbar is rendered invisible and afterwards is set to visible
stepSize = window.getComputedStyle(jQuery("body").get(0))["font-size"];
if (stepSize.endsWith("px")) {
stepSize = stepSize.substr(0, stepSize.length - 2);
}
stepSize = parseInt(stepSize);
}
}
$ffsize.remove();
if (Device.browser.webkit) {
// document.width - was not supported by Chrome 17 anymore, but works again with Chrome from 18 to 30, and does not work in chrom 31.
if (!document.width) {
stepSize = Math.round(40 / (window.outerWidth / jQuery(document).width()));
} else {
stepSize = Math.round(40 / (document.width / jQuery(document).width()));
//Log.debug( stepSize + " ****************************STEP SIZE*************************************************************");
}
}
if (this.getVertical()) {
if (Device.browser.firefox) {
this._iFactor = stepSize;
} else if (Device.browser.webkit) {
this._iFactor = stepSize;
} else {
this._iFactor = Math.floor(iScrollBarSize * 0.125);
}
this._iFactorPage = Device.browser.firefox ? iScrollBarSize - stepSize : Math.floor(iScrollBarSize * 0.875);
} else {
if (Device.browser.firefox) {
this._iFactor = 10;
this._iFactorPage = Math.floor(iScrollBarSize * 0.8);
} else if (Device.browser.webkit) {
this._iFactor = stepSize;
this._iFactorPage = Math.floor(iScrollBarSize * 0.875);
} else {
this._iFactor = 7;
this._iFactorPage = iScrollBarSize - 14;
}
}
this._$ScrollDomRef = this.$("sb");
if (this._bStepMode) {
if (this.getVertical()) {
// calculate the height of the content size => scroll bar height + (steps * browser step size)
var iSize = this._iSteps * this._iFactor;
if (iSize > this._iMaxContentDivSize) {
this._iFactor = this._iFactor / (iSize / this._iMaxContentDivSize);
}
var iContentSize = this._$ScrollDomRef.height() + Math.ceil(this._iSteps * this._iFactor);
// set the content size
this._$ScrollDomRef.find("div").height(iContentSize);
} else {
// calculate the height of the content size => scroll bar size + (steps * browser step size)
var iContentSize = this._$ScrollDomRef.width() + this._iSteps * this._iFactor;
// set the content size
this._$ScrollDomRef.find("div").width(iContentSize);
}
}
this.setCheckedScrollPosition(this.getScrollPosition() ? this.getScrollPosition() : 0, true);
this._$ScrollDomRef.on("scroll", this.onscroll);
if (EventSimulation.touchEventMode === "ON") {
this._bSkipTouchHandling = true;
var oContent = {
width:0,
height:0
};
oContent[this.getVertical() ? "height" : "width"] = this._bStepMode ? (this.getSteps() * this._iTouchStepTreshold) : parseInt(this.getContentSize());
this._oTouchScroller.setDimensions(0, 0, oContent.width, oContent.height);
var oElement = this._$ScrollDomRef.get(0);
if (oElement) {
var oRect = oElement.getBoundingClientRect();
this._oTouchScroller.setPosition(oRect.left + oElement.clientLeft, oRect.top + oElement.clientTop);
this._bSkipTouchHandling = false;
}
}
};
//=============================================================================
// CONTROL EVENT HANDLING
//=============================================================================
/**
* Event object contains detail (for Firefox and Opera), and wheelData (for Internet Explorer, Safari, and Opera).
* Scrolling down is a positive number for detail, but a negative number for wheelDelta.
* @param {jQuery.Event} oEvent Event object contains detail (for Firefox and Opera), and wheelData (for Internet Explorer, Safari, and Opera).
* @private
*/
ScrollBar.prototype.onmousewheel = function(oEvent) {
// ignore the mousewheel events when the scrollbar is not visible
if (this.$().is(":visible")) {
// So let's scale and make negative value for all scroll down in all browsers.
var oOriginalEvent = oEvent.originalEvent;
var wheelData = oOriginalEvent.detail ? oOriginalEvent.detail : oOriginalEvent.wheelDelta * (-1) / 40;
// find out if the user is scrolling up= back or down= forward.
var bForward = wheelData > 0 ? true : false;
if (this._$ScrollDomRef[0] && this._$ScrollDomRef[0].contains(oEvent.target)) {
this._doScroll(ScrollBarAction.MouseWheel, bForward);
} else {
this._bMouseWheel = true;
var pos = null;
if (this._bStepMode) {
pos = wheelData + this._iOldStep;
} else {
pos = wheelData * this._iFactor + this._iOldScrollPos;
}
this.setCheckedScrollPosition(pos, true);
}
// prevent the default behavior
oEvent.preventDefault();
oEvent.stopPropagation();
return false;
}
};
/**
* Touch start handler. Called when the "touch start" event occurs on this control.
* @param {jQuery.Event} oEvent Touch Event object
* @private
*/
ScrollBar.prototype.ontouchstart = function(oEvent) {
// Don't react if initial down happens on a form element
var aTouches = oEvent.touches;
var oFirstTouch = aTouches[0];
if (oFirstTouch && oFirstTouch.target && oFirstTouch.target.tagName.match(/input|textarea|select/i)) {
return;
}
if (this._oTouchScroller) {
this._oTouchScroller.doTouchStart(aTouches, oEvent.timeStamp);
}
if (aTouches.length == 1) {
oEvent.preventDefault();
}
};
/**
* Touch move handler. Called when the "touch move" event occurs on this control.
* @param {jQuery.Event} oEvent Touch Event object
* @private
*/
ScrollBar.prototype.ontouchmove = function(oEvent) {
if (this._oTouchScroller) {
this._oTouchScroller.doTouchMove(oEvent.touches, oEvent.timeStamp, oEvent.scale);
}
};
/**
* Touch end handler. Called when the "touch end" event occurs on this control.
* @param {jQuery.Event} oEvent Touch Event object
* @private
*/
ScrollBar.prototype.ontouchend = function(oEvent) {
if (this._oTouchScroller) {
this._oTouchScroller.doTouchEnd(oEvent.timeStamp);
}
};
/**
* Touch cancel handler. Called when the "touch cancel" event occurs on this control.
* @param {jQuery.Event} oEvent Touch Event object
* @private
*/
ScrollBar.prototype.ontouchcancel = function(oEvent) {
if (this._oTouchScroller) {
this._oTouchScroller.doTouchEnd(oEvent.timeStamp);
}
};
/**
* Handles the Scroll event.
*
* @param {jQuery.Event} oEvent Event object
* @private
*/
ScrollBar.prototype.onscroll = function(oEvent) {
//Log.debug("*****************************onScroll************************ suppress SCROLL: " + this._bSuppressScroll );
if (this._bSuppressScroll) {
this._bSuppressScroll = false;
oEvent.preventDefault();
oEvent.stopPropagation();
return false;
}
// Set new Scroll position
var iScrollPos = null;
if (this._$ScrollDomRef) {
if (this.getVertical()) {
iScrollPos = Math.round(this._$ScrollDomRef.scrollTop());
} else {
iScrollPos = Math.round(this._$ScrollDomRef.scrollLeft());
if ( Device.browser.firefox && this._bRTL ) {
iScrollPos = Math.abs(iScrollPos);
} else if ( Device.browser.webkit && this._bRTL ) {
var oScrollDomRef = this._$ScrollDomRef.get(0);
iScrollPos = oScrollDomRef.scrollWidth - oScrollDomRef.clientWidth - oScrollDomRef.scrollLeft;
}
}
}
var iDelta = iScrollPos - this._iOldScrollPos;
var bForward = iDelta > 0 ? true : false;
if (iDelta < 0) {
iDelta = iDelta * (-1);
}
var eAction = ScrollBarAction.Drag;
if (iDelta == this._iFactor) {
eAction = ScrollBarAction.Step;
} else if (iDelta == this._iFactorPage) {
eAction = ScrollBarAction.Page;
} else if (this._bMouseWheel) {
eAction = ScrollBarAction.MouseWheel;
}
// Proceed scroll
if (this._bLargeDataScrolling && eAction === ScrollBarAction.Drag) {
this._eAction = eAction;
this._bForward = bForward;
} else {
this._doScroll(eAction, bForward);
}
oEvent.preventDefault();
oEvent.stopPropagation();
return false;
};
// TODO: IE removal ==> check if still needed
ScrollBar.prototype._onScrollTimeout = function(){
this._scrollTimeout = undefined;
this._doScroll(this._eAction, this._bForward);
this._eAction = undefined;
this._bForward = undefined;
this._bTouchScroll = undefined;
};
ScrollBar.prototype.onmouseup = function() {
if (this._bLargeDataScrolling && (this._eAction || this._bForward || this._bTouchScroll)) {
this._doScroll(this._eAction, this._bForward);
this._eAction = undefined;
this._bForward = undefined;
this._bTouchScroll = undefined;
}
};
ScrollBar.prototype.ontouchend = ScrollBar.prototype.onmouseup;
/**
* Handler for the touch scroller instance. Called only when touch mode is enabled.
*
* @param {number} left Horizontal scroll position
* @param {number} top Vertical scroll position
* @param {number} zoom The zoom level
* @private
*/
ScrollBar.prototype._handleTouchScroll = function(iLeft, iTop, iZoom) {
if (this._bSkipTouchHandling) {
return;
}
var iValue = this.getVertical() ? iTop : iLeft;
var iPos;
if (this._bStepMode) {
iPos = Math.max(Math.round(iValue / this._iTouchStepTreshold), 0);
} else {
iPos = Math.round(iValue);
}
if (this._iLastTouchScrollerPosition !== iPos) {
this._iLastTouchScrollerPosition = iPos;
this.setCheckedScrollPosition(iPos, true);
if (this._bLargeDataScrolling) {
this._bTouchScroll = true;
} else {
this.fireScroll();
}
}
};
//=============================================================================
// PUBLIC API METHODS
//=============================================================================
/**
* Unbinds the mouse wheel scroll event of the control that has the scrollbar
*
* @param {string} oOwnerDomRef
* Dom ref of the Control that uses the scrollbar
* @public
*/
ScrollBar.prototype.unbind = function (oOwnerDomRef) {
if (oOwnerDomRef) {
this._$OwnerDomRef = jQuery(oOwnerDomRef);
if (this.getVertical()) {
this._$OwnerDomRef.off(Device.browser.firefox ? "DOMMouseScroll" : "mousewheel", this.onmousewheel);
}
if (EventSimulation.touchEventMode === "ON") {
this._$OwnerDomRef.off(this._getTouchEventType("touchstart"), this.ontouchstart);
this._$OwnerDomRef.off(this._getTouchEventType("touchmove"), this.ontouchmove);
this._$OwnerDomRef.off(this._getTouchEventType("touchend"), this.ontouchend);
this._$OwnerDomRef.off(this._getTouchEventType("touchcancel"), this.ontouchcancel);
}
}
};
/**
* Binds the mouse wheel scroll event of the control that has the scrollbar to the scrollbar itself.
*
* @param {string} oOwnerDomRef
* Dom ref of the control that uses the scrollbar
* @public
*/
ScrollBar.prototype.bind = function (oOwnerDomRef) {
if (oOwnerDomRef) {
this._$OwnerDomRef = jQuery(oOwnerDomRef);
if (this.getVertical()) {
this._$OwnerDomRef.on(Device.browser.firefox ? "DOMMouseScroll" : "mousewheel", this.onmousewheel);
}
if (EventSimulation.touchEventMode === "ON") {
this._$OwnerDomRef.on(this._getTouchEventType("touchstart"), this.ontouchstart);
this._$OwnerDomRef.on(this._getTouchEventType("touchmove"), this.ontouchmove);
this._$OwnerDomRef.on(this._getTouchEventType("touchend"), this.ontouchend);
this._$OwnerDomRef.on(this._getTouchEventType("touchcancel"), this.ontouchcancel);
}
}
};
/**
* Returns the event type for a given touch event type, based on the current touch event mode
* (EventSimulation.touchEventMode).
*
* @param {string} sType The touch event to convert
* @return {string} The converted event type.
* @private
*/
ScrollBar.prototype._getTouchEventType = function (sType) {
return EventSimulation.touchEventMode === "SIM" ? ("sap" + sType) : sType;
};
/**
* Page Up is used to scroll one page back.
*
* @public
*/
ScrollBar.prototype.pageUp = function() {
// call on scroll
this._doScroll(ScrollBarAction.Page, false);
};
/**
* Page Down is used to scroll one page forward.
*
* @public
*/
ScrollBar.prototype.pageDown = function() {
// call on scroll
this._doScroll(ScrollBarAction.Page, true);
};
//=============================================================================
// OVERRIDE OF SETTERS
//=============================================================================
/*
* @see JSDoc generated by SAPUI5 control API generator
*/
ScrollBar.prototype.setScrollPosition = function (scrollPosition) {
if (this._$ScrollDomRef) {
this.setCheckedScrollPosition(scrollPosition, true);
} else {
this.setProperty("scrollPosition", scrollPosition);
}
return this;
};
/*
* After the Scrollbar is rendered, we check the validity of the scroll position and set Scroll Left and ScrollTop.
* @private
*/
ScrollBar.prototype.setCheckedScrollPosition = function (scrollPosition, callScrollEvent) {
var iCheckedSP = Math.max(scrollPosition, 0);
if ( this._bStepMode === undefined) {
this._bStepMode = !this.getContentSize();
}
var iScrollPos = iCheckedSP;
if ( this._bStepMode) {
iCheckedSP = Math.min(iCheckedSP, this.getSteps());
// STEPS MODE - Calculate the position in PX
iScrollPos = iCheckedSP * this._iFactor;
}
iCheckedSP = Math.round(iCheckedSP);
this._bSuppressScroll = !callScrollEvent;
this.setProperty("scrollPosition", iCheckedSP, true);
if ( this.getVertical()) {
this._$ScrollDomRef.scrollTop(iScrollPos);
} else {
if ( Device.browser.firefox && this._bRTL ) {
this._$ScrollDomRef.scrollLeft(-iScrollPos);
} else if ( Device.browser.webkit && this._bRTL ) {
var oScrollDomRef = this._$ScrollDomRef.get(0);
this._$ScrollDomRef.scrollLeft(oScrollDomRef.scrollWidth - oScrollDomRef.clientWidth - iScrollPos);
} else {
this._$ScrollDomRef.scrollLeft(iScrollPos);
}
}
if (EventSimulation.touchEventMode === "ON") {
var value = iCheckedSP;
if (this._bStepMode) {
value = Math.round(iCheckedSP * this._iTouchStepTreshold);
}
this._oTouchScroller.__scrollTop = this.getVertical() ? value : 0;
this._oTouchScroller.__scrollLeft = this.getVertical() ? 0 : value;
}
};
/*
* @see JSDoc generated by SAPUI5 control API generator
*/
ScrollBar.prototype.setContentSize = function (sContentSize) {
// Trigger the rerendering when switching the from step mode.
this.setProperty("contentSize", sContentSize, true);
this._bStepMode = false;
var $SbCnt = this.$("sbcnt");
if ($SbCnt) {
if (this.getVertical()) {
$SbCnt.height(sContentSize);
} else {
$SbCnt.width(sContentSize);
}
}
return this;
};
//=============================================================================
// PRIVATE METHODS
//=============================================================================
/**
* Process scroll events and fire scroll event
* @param {sap.ui.core.ScrollBarAction} sAction Action type that can be mouse wheel, Drag, Step or Page.
* @param {boolean} bForward Scroll Direction - forward or back
* @private
*/
ScrollBar.prototype._doScroll = function(sAction, bForward) {
// Get new scroll position
var iScrollPos = null;
if (this._$ScrollDomRef) {
if (this.getVertical()) {
iScrollPos = Math.round(this._$ScrollDomRef.scrollTop());
} else {
iScrollPos = Math.round(this._$ScrollDomRef.scrollLeft());
if (Device.browser.firefox && this._bRTL ) {
iScrollPos = Math.abs(iScrollPos);
} else if ( Device.browser.webkit && this._bRTL ) {
var oScrollDomRef = this._$ScrollDomRef.get(0);
iScrollPos = oScrollDomRef.scrollWidth - oScrollDomRef.clientWidth - oScrollDomRef.scrollLeft;
}
}
}
if (this._bStepMode) {
// STEP MODE
var iStep = Math.round(iScrollPos / this._iFactor);
var iOldStep = this._iOldStep;
if (iOldStep !== iStep) {
// Set new scrollposition without the rerendering
this.setCheckedScrollPosition(iStep, false);
Log.debug("-----STEPMODE-----: New Step: " + iStep + " --- Old Step: " + iOldStep + " --- Scroll Pos in px: " + iScrollPos + " --- Action: " + sAction + " --- Direction is forward: " + bForward);
this.fireScroll({ action: sAction, forward: bForward, newScrollPos: iStep, oldScrollPos: iOldStep});
this._iOldStep = iStep;
}
} else {
// Set new scroll position without the rerendering:
iScrollPos = Math.round(iScrollPos);
this.setProperty("scrollPosition", iScrollPos, true);
Log.debug("-----PIXELMODE-----: New ScrollPos: " + iScrollPos + " --- Old ScrollPos: " + this._iOldScrollPos + " --- Action: " + sAction + " --- Direction is forward: " + bForward);
this.fireScroll({ action: sAction, forward: bForward, newScrollPos: iScrollPos, oldScrollPos: this._iOldScrollPos});
}
this._bSuppressScroll = false;
this._iOldScrollPos = iScrollPos;
this._bMouseWheel = false;
// notify for a scroll event
Interaction.notifyScrollEvent({type: sAction});
};
ScrollBar.prototype.onThemeChanged = function() {
this.rerender();
};
/**
* return the native scroll position without any browser specific correction of
* the scroll position value (firefox & RTL => negative value / webkit & RTL =>
* positive value not beginning with 0 because 0 is left and not as expected
* right for webkit RTL mode).
* @return {int} native scroll position
* @private
*/
ScrollBar.prototype.getNativeScrollPosition = function() {
if (this._$ScrollDomRef) {
if (this.getVertical()) {
return Math.round(this._$ScrollDomRef.scrollTop());
} else {
return Math.round(this._$ScrollDomRef.scrollLeft());
}
}
return 0;
};
/**
* sets the scroll position directly
* @param {int} iNativeScrollPos new native scroll position
* @private
*/
ScrollBar.prototype.setNativeScrollPosition = function(iNativeScrollPos) {
var iScrollPos = Math.round(iNativeScrollPos);
if (this._$ScrollDomRef) {
if (this.getVertical()) {
this._$ScrollDomRef.scrollTop(iScrollPos);
} else {
this._$ScrollDomRef.scrollLeft(iScrollPos);
}
}
};
return ScrollBar;
});