devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
575 lines (574 loc) • 23 kB
JavaScript
/**
* DevExtreme (esm/ui/scheduler/workspaces/ui.scheduler.virtual_scrolling.js)
* Version: 21.1.4
* Build date: Mon Jun 21 2021
*
* Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import _extends from "@babel/runtime/helpers/esm/extends";
import domAdapter from "../../../core/dom_adapter";
import eventsEngine from "../../../events/core/events_engine";
import {
getWindow
} from "../../../core/utils/window";
import {
addNamespace
} from "../../../events/utils/index";
import {
isDefined
} from "../../../core/utils/type";
var DEFAULT_CELL_HEIGHT = 50;
var MIN_CELL_WIDTH = 1;
var MIN_SCROLL_OFFSET = 10;
var VIRTUAL_APPOINTMENTS_RENDER_TIMEOUT = 30;
var DOCUMENT_SCROLL_EVENT_NAMESPACE = addNamespace("scroll", "dxSchedulerVirtualScrolling");
var scrollingOrientations = {
vertical: "vertical",
horizontal: "horizontal",
both: "both"
};
var DefaultScrollingOrientation = scrollingOrientations.both;
export default class VirtualScrollingDispatcher {
constructor(workspace) {
this._workspace = workspace;
this._rowHeight = this.getCellHeight();
this._cellWidth = this.getCellWidth();
this._renderer = new Renderer(this.workspace);
this._createVirtualScrolling();
this._attachScrollableEvents()
}
get workspace() {
return this._workspace
}
get isRTL() {
return this.workspace._isRTL()
}
get renderer() {
return this._renderer
}
get isVirtualScrolling() {
return this.workspace.isVirtualScrolling()
}
get verticalVirtualScrolling() {
return this._verticalVirtualScrolling
}
set verticalVirtualScrolling(value) {
this._verticalVirtualScrolling = value
}
get horizontalVirtualScrolling() {
return this._horizontalVirtualScrolling
}
set horizontalVirtualScrolling(value) {
this._horizontalVirtualScrolling = value
}
get document() {
return domAdapter.getDocument()
}
get height() {
return this.workspace.invoke("getOption", "height")
}
get width() {
return this.workspace.invoke("getOption", "width")
}
get rowHeight() {
return this._rowHeight
}
set rowHeight(value) {
this._rowHeight = value
}
get viewportHeight() {
return this.height ? this.workspace.$element().height() : getWindow().innerHeight
}
get cellWidth() {
return this._cellWidth
}
set cellWidth(value) {
this._cellWidth = value
}
get viewportWidth() {
return this.width ? this.workspace.$element().width() : getWindow().innerWidth
}
get topVirtualRowsCount() {
var _this$verticalScrolli;
return (null === (_this$verticalScrolli = this.verticalScrollingState) || void 0 === _this$verticalScrolli ? void 0 : _this$verticalScrolli.virtualItemCountBefore) > 0 ? 1 : 0
}
get leftVirtualCellsCount() {
var _this$horizontalScrol, _this$horizontalScrol2;
var virtualItemsCount = !this.isRTL ? null === (_this$horizontalScrol = this.horizontalScrollingState) || void 0 === _this$horizontalScrol ? void 0 : _this$horizontalScrol.virtualItemCountBefore : null === (_this$horizontalScrol2 = this.horizontalScrollingState) || void 0 === _this$horizontalScrol2 ? void 0 : _this$horizontalScrol2.virtualItemCountAfter;
return virtualItemsCount > 0 ? 1 : 0
}
get virtualRowOffset() {
var _this$verticalScrolli2;
return (null === (_this$verticalScrolli2 = this.verticalScrollingState) || void 0 === _this$verticalScrolli2 ? void 0 : _this$verticalScrolli2.virtualItemSizeBefore) || 0
}
get virtualCellOffset() {
var _this$horizontalScrol3;
return (null === (_this$horizontalScrol3 = this.horizontalScrollingState) || void 0 === _this$horizontalScrol3 ? void 0 : _this$horizontalScrol3.virtualItemSizeBefore) || 0
}
get scrollingState() {
var _this$verticalVirtual, _this$horizontalVirtu;
return {
vertical: null === (_this$verticalVirtual = this.verticalVirtualScrolling) || void 0 === _this$verticalVirtual ? void 0 : _this$verticalVirtual.state,
horizontal: null === (_this$horizontalVirtu = this.horizontalVirtualScrolling) || void 0 === _this$horizontalVirtu ? void 0 : _this$horizontalVirtu.state
}
}
get verticalScrollingState() {
return this.scrollingState.vertical
}
get horizontalScrollingState() {
return this.scrollingState.horizontal
}
get scrollingOrientation() {
return this.workspace.option("scrolling.orientation") || DefaultScrollingOrientation
}
get verticalScrollingAllowed() {
return this.scrollingOrientation === scrollingOrientations.vertical || this.scrollingOrientation === scrollingOrientations.both
}
get horizontalScrollingAllowed() {
return this.scrollingOrientation === scrollingOrientations.horizontal || this.scrollingOrientation === scrollingOrientations.both
}
getRenderState() {
var _this$verticalVirtual2, _this$horizontalVirtu2;
var verticalRenderState = (null === (_this$verticalVirtual2 = this.verticalVirtualScrolling) || void 0 === _this$verticalVirtual2 ? void 0 : _this$verticalVirtual2.getRenderState()) || {};
var horizontalRenderState = (null === (_this$horizontalVirtu2 = this.horizontalVirtualScrolling) || void 0 === _this$horizontalVirtu2 ? void 0 : _this$horizontalVirtu2.getRenderState()) || {};
return _extends({}, verticalRenderState, horizontalRenderState)
}
getCellHeight() {
var cellHeight = this.workspace.getCellHeight(false);
var result = cellHeight > 0 ? cellHeight : DEFAULT_CELL_HEIGHT;
return Math.floor(result)
}
getCellWidth() {
var cellWidth = this.workspace.getCellWidth();
var minCellWidth = this.workspace.getCellMinWidth();
if (!cellWidth || cellWidth < minCellWidth) {
cellWidth = minCellWidth
}
var result = cellWidth > 0 ? cellWidth : MIN_CELL_WIDTH;
return Math.floor(result)
}
calculateCoordinatesByDataAndPosition(cellData, position, date, isCalculateTime, isVerticalDirectionView) {
var {
_workspace: workSpace
} = this;
var {
rowIndex: rowIndex,
columnIndex: columnIndex
} = position;
var {
startDate: startDate,
endDate: endDate,
allDay: allDay
} = cellData;
var timeToScroll = date.getTime();
var cellStartTime = startDate.getTime();
var cellEndTime = endDate.getTime();
var scrollInCell = allDay || !isCalculateTime ? 0 : (timeToScroll - cellStartTime) / (cellEndTime - cellStartTime);
var cellWidth = this.getCellWidth();
var rowHeight = this.getCellHeight();
var top = isVerticalDirectionView ? (rowIndex + scrollInCell) * rowHeight : rowIndex * rowHeight;
var left = isVerticalDirectionView ? columnIndex * cellWidth : (columnIndex + scrollInCell) * cellWidth;
if (workSpace.option("rtlEnabled")) {
left = workSpace.getScrollableOuterWidth() - left
}
return {
top: top,
left: left
}
}
dispose() {
if (this._onScrollHandler) {
eventsEngine.off(this.document, DOCUMENT_SCROLL_EVENT_NAMESPACE, this._onScrollHandler)
}
}
_createVirtualScrolling() {
if (this.verticalScrollingAllowed) {
this.verticalVirtualScrolling = new VerticalVirtualScrolling({
workspace: this.workspace,
viewportHeight: this.viewportHeight,
rowHeight: this.rowHeight
})
}
if (this.horizontalScrollingAllowed) {
this.horizontalVirtualScrolling = new HorizontalVirtualScrolling({
workspace: this.workspace,
viewportWidth: this.viewportWidth,
cellWidth: this.cellWidth
})
}
}
_attachScrollableEvents() {
if (this.horizontalScrollingAllowed || this.verticalScrollingAllowed) {
if (this.height || this.horizontalScrollingAllowed) {
this._attachScrollableScroll()
}
if (!this.height) {
this._attachWindowScroll()
}
}
}
_attachScrollableScroll() {
var scrollable = this.workspace.getScrollable();
var currentOnScroll = scrollable.option("onScroll");
scrollable.option("onScroll", e => {
null === currentOnScroll || void 0 === currentOnScroll ? void 0 : currentOnScroll.apply(scrollable, [e]);
this._process(null === e || void 0 === e ? void 0 : e.scrollOffset)
})
}
_attachWindowScroll() {
var window = getWindow();
this._onScrollHandler = this.workspace._createAction(() => {
var {
scrollX: scrollX,
scrollY: scrollY
} = window;
if (scrollX >= MIN_SCROLL_OFFSET || scrollY >= MIN_SCROLL_OFFSET) {
this._process({
left: scrollX,
top: scrollY
})
}
});
eventsEngine.on(this.document, DOCUMENT_SCROLL_EVENT_NAMESPACE, this._onScrollHandler)
}
_process(scrollPosition) {
if (scrollPosition) {
var _this$verticalVirtual3, _this$horizontalVirtu3;
var {
left: left,
top: top
} = scrollPosition;
var verticalStateChanged = isDefined(top) && (null === (_this$verticalVirtual3 = this.verticalVirtualScrolling) || void 0 === _this$verticalVirtual3 ? void 0 : _this$verticalVirtual3.updateState(top));
var horizontalStateChanged = isDefined(left) && (null === (_this$horizontalVirtu3 = this.horizontalVirtualScrolling) || void 0 === _this$horizontalVirtu3 ? void 0 : _this$horizontalVirtu3.updateState(left));
if (verticalStateChanged || horizontalStateChanged) {
this.renderer.updateRender()
}
}
}
updateDimensions(isForce) {
var cellHeight = this.getCellHeight(false);
var needUpdateVertical = this.verticalScrollingAllowed && cellHeight !== this.rowHeight;
if (needUpdateVertical || isForce) {
var _this$verticalVirtual4;
this.rowHeight = cellHeight;
null === (_this$verticalVirtual4 = this.verticalVirtualScrolling) || void 0 === _this$verticalVirtual4 ? void 0 : _this$verticalVirtual4.reinitState(cellHeight, isForce)
}
var cellWidth = this.getCellWidth();
var needUpdateHorizontal = this.horizontalScrollingAllowed && cellWidth !== this.cellWidth;
if (needUpdateHorizontal || isForce) {
var _this$horizontalVirtu4;
this.cellWidth = cellWidth;
null === (_this$horizontalVirtu4 = this.horizontalVirtualScrolling) || void 0 === _this$horizontalVirtu4 ? void 0 : _this$horizontalVirtu4.reinitState(cellWidth, isForce)
}
if (needUpdateVertical || needUpdateHorizontal) {
this.renderer._renderGrid()
}
}
}
class VirtualScrollingBase {
constructor(options) {
this._workspace = options.workspace;
this._state = this.defaultState;
this._viewportSize = options.viewportSize;
this._itemSize = options.itemSize;
this._position = -1;
this._itemSizeChanged = false;
this.updateState(0)
}
get viewportSize() {
return this._viewportSize
}
get itemSize() {
return this._itemSize
}
set itemSize(value) {
this._itemSizeChanged = this._itemSize !== value;
this._itemSize = value
}
get state() {
return this._state
}
set state(value) {
this._state = value
}
get startIndex() {
return this.state.startIndex
}
get pageSize() {
return Math.ceil(this.viewportSize / this.itemSize)
}
get outlineCount() {
return Math.floor(this.pageSize / 2)
}
get workspace() {
return this._workspace
}
get groupCount() {
return this.workspace._getGroupCount()
}
get isVerticalGrouping() {
return this.workspace._isVerticalGroupedWorkSpace()
}
get defaultState() {
return {
prevPosition: 0,
startIndex: -1,
itemCount: 0,
virtualItemCountBefore: 0,
virtualItemCountAfter: 0,
outlineCountBefore: 0,
outlineCountAfter: 0,
virtualItemSizeBefore: 0,
virtualItemSizeAfter: 0,
outlineSizeBefore: 0,
outlineSizeAfter: 0
}
}
get maxScrollPosition() {
return this.getTotalItemCount() * this.itemSize - this.viewportSize
}
get position() {
return this._position
}
set position(value) {
this._position = value
}
needUpdateState(position) {
var {
prevPosition: prevPosition,
startIndex: startIndex
} = this.state;
var isFirstInitialization = startIndex < 0;
if (isFirstInitialization) {
return true
}
var isStartIndexChanged = false;
if (this._validateAndSavePosition(position)) {
if (0 === position || position === this.maxScrollPosition) {
return true
}
var currentPosition = prevPosition;
var currentItemsCount = Math.floor(currentPosition / this.itemSize);
var itemsCount = Math.floor(position / this.itemSize);
isStartIndexChanged = Math.abs(currentItemsCount - itemsCount) >= this.outlineCount
}
return isStartIndexChanged
}
_validateAndSavePosition(position) {
if (!isDefined(position)) {
return false
}
var result = this.position !== position;
this.position = position;
return result
}
_correctPosition(position) {
return position >= 0 ? Math.min(position, this.maxScrollPosition) : -1
}
updateState(position, isForce) {
position = this._correctPosition(position);
if (!this.needUpdateState(position) && !isForce) {
return false
}
var itemsInfoBefore = this._calcItemInfoBefore(position);
var itemsDeltaBefore = this._calcItemDeltaBefore(itemsInfoBefore);
var {
outlineCountAfter: outlineCountAfter,
virtualItemCountAfter: virtualItemCountAfter,
itemCountWithAfter: itemCountWithAfter
} = this._calcItemInfoAfter(itemsDeltaBefore);
var {
virtualItemCountBefore: virtualItemCountBefore,
outlineCountBefore: outlineCountBefore
} = itemsInfoBefore;
var itemCount = outlineCountBefore + itemCountWithAfter + outlineCountAfter;
var itemCountBefore = Math.floor(position / this.itemSize);
this.state.prevPosition = itemCountBefore * this.itemSize;
this.state.startIndex = itemCountBefore - outlineCountBefore;
this.state.virtualItemCountBefore = virtualItemCountBefore;
this.state.outlineCountBefore = outlineCountBefore;
this.state.itemCount = itemCount;
this.state.outlineCountAfter = outlineCountAfter;
this.state.virtualItemCountAfter = virtualItemCountAfter;
this._updateStateCore();
return true
}
reinitState(itemSize, isForceUpdate) {
var {
position: position
} = this;
this.itemSize = itemSize;
this.updateState(0, isForceUpdate);
if (position > 0) {
this.updateState(position, isForceUpdate)
}
}
_calcItemInfoBefore(position) {
var virtualItemCountBefore = Math.floor(position / this.itemSize);
var outlineCountBefore = Math.min(virtualItemCountBefore, this.outlineCount);
virtualItemCountBefore -= outlineCountBefore;
return {
virtualItemCountBefore: virtualItemCountBefore,
outlineCountBefore: outlineCountBefore
}
}
_calcItemDeltaBefore(itemInfoBefore) {
var {
virtualItemCountBefore: virtualItemCountBefore,
outlineCountBefore: outlineCountBefore
} = itemInfoBefore;
var totalItemCount = this.getTotalItemCount();
return totalItemCount - virtualItemCountBefore - outlineCountBefore
}
getTotalItemCount() {
throw "getTotalItemCount method should be implemented"
}
getRenderState() {
throw "getRenderState method should be implemented"
}
_calcItemInfoAfter(itemsDeltaBefore) {
var itemCountWithAfter = itemsDeltaBefore >= this.pageSize ? this.pageSize : itemsDeltaBefore;
var virtualItemCountAfter = itemsDeltaBefore - itemCountWithAfter;
var outlineCountAfter = virtualItemCountAfter > 0 ? Math.min(virtualItemCountAfter, this.outlineCount) : 0;
if (virtualItemCountAfter > 0) {
virtualItemCountAfter -= outlineCountAfter
}
return {
virtualItemCountAfter: virtualItemCountAfter,
outlineCountAfter: outlineCountAfter,
itemCountWithAfter: itemCountWithAfter
}
}
_updateStateCore() {
var {
state: state
} = this;
var virtualItemCountBefore = state.virtualItemCountBefore;
var virtualItemCountAfter = state.virtualItemCountAfter;
var outlineCountBefore = state.outlineCountBefore;
var outlineCountAfter = state.outlineCountAfter;
var prevVirtualItemSizeBefore = state.virtualItemSizeBefore;
var prevVirtualItemSizeAfter = state.virtualItemSizeAfter;
var prevOutlineSizeBefore = state.outlineSizeBefore;
var prevOutlineSizeAfter = state.outlineSizeAfter;
var virtualItemSizeBefore = this.itemSize * virtualItemCountBefore;
var virtualItemSizeAfter = this.itemSize * virtualItemCountAfter;
var outlineSizeBefore = this.itemSize * outlineCountBefore;
var outlineSizeAfter = this.itemSize * outlineCountAfter;
var prevVirtualSizeBefore = prevVirtualItemSizeBefore + prevOutlineSizeBefore;
var virtualSizeBefore = virtualItemSizeBefore + outlineSizeBefore;
var prevVirtualSizeAfter = prevVirtualItemSizeAfter + prevOutlineSizeAfter;
var virtualSizeAfter = virtualItemSizeAfter + outlineSizeAfter;
var isAppend = prevVirtualSizeBefore < virtualSizeBefore;
var isPrepend = prevVirtualSizeAfter < virtualSizeAfter;
var needAddItems = this._itemSizeChanged || isAppend || isPrepend;
if (needAddItems) {
this._updateStateVirtualItems(virtualItemSizeBefore, virtualItemSizeAfter)
}
}
_updateStateVirtualItems(virtualItemSizeBefore, virtualItemSizeAfter) {
var {
state: state
} = this;
state.virtualItemSizeBefore = virtualItemSizeBefore;
state.virtualItemSizeAfter = virtualItemSizeAfter
}
}
class VerticalVirtualScrolling extends VirtualScrollingBase {
constructor(options) {
super({
workspace: options.workspace,
viewportSize: options.viewportHeight,
itemSize: options.rowHeight
})
}
get prevTopPosition() {
return this.state.prevPosition
}
get rowCount() {
return this.state.itemCount
}
get topVirtualRowCount() {
return this.state.virtualItemCountBefore
}
get bottomVirtualRowCount() {
return this.state.virtualItemCountAfter
}
getTotalItemCount() {
return this.workspace._getTotalRowCount(this.groupCount, this.isVerticalGrouping)
}
getRenderState() {
return {
topVirtualRowHeight: this.state.virtualItemSizeBefore,
bottomVirtualRowHeight: this.state.virtualItemSizeAfter,
startRowIndex: this.state.startIndex,
rowCount: this.state.itemCount,
startIndex: this.state.startIndex
}
}
}
class HorizontalVirtualScrolling extends VirtualScrollingBase {
constructor(options) {
super({
workspace: options.workspace,
viewportSize: options.viewportWidth,
itemSize: options.cellWidth
})
}
get isRTL() {
return this.workspace._isRTL()
}
getTotalItemCount() {
return this.workspace._getTotalCellCount(this.groupCount, this.isVerticalGrouping)
}
getRenderState() {
return {
leftVirtualCellWidth: this.state.virtualItemSizeBefore,
rightVirtualCellWidth: this.state.virtualItemSizeAfter,
startCellIndex: this.state.startIndex,
cellCount: this.state.itemCount,
cellWidth: this.itemSize
}
}
_updateStateVirtualItems(virtualItemSizeBefore, virtualItemSizeAfter) {
if (!this.isRTL) {
super._updateStateVirtualItems(virtualItemSizeBefore, virtualItemSizeAfter)
} else {
var {
state: state
} = this;
state.virtualItemSizeAfter = virtualItemSizeBefore;
state.virtualItemSizeBefore = virtualItemSizeAfter;
state.startIndex = this.getTotalItemCount() - this.startIndex - this.state.itemCount
}
}
}
class Renderer {
constructor(workspace) {
this._workspace = workspace;
this._renderAppointmentTimeout = null
}
getRenderTimeout() {
return VIRTUAL_APPOINTMENTS_RENDER_TIMEOUT
}
get workspace() {
return this._workspace
}
updateRender() {
this._renderGrid();
this._renderAppointments()
}
_renderGrid() {
this.workspace.renderRWorkspace(false)
}
_renderAppointments() {
var renderTimeout = this.getRenderTimeout();
if (renderTimeout >= 0) {
clearTimeout(this._renderAppointmentTimeout);
this._renderAppointmentTimeout = setTimeout(() => this.workspace.updateAppointments(), renderTimeout)
} else {
this.workspace.updateAppointments()
}
}
}