devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
606 lines (605 loc) • 24.6 kB
JavaScript
/**
* DevExtreme (esm/ui/scheduler/rendering_strategies/ui.scheduler.appointments.strategy.base.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 BasePositioningStrategy from "./ui.scheduler.appointmentsPositioning.strategy.base";
import AdaptivePositioningStrategy from "./ui.scheduler.appointmentsPositioning.strategy.adaptive";
import {
extend
} from "../../../core/utils/extend";
import dateUtils from "../../../core/utils/date";
import {
isNumeric,
isObject
} from "../../../core/utils/type";
import {
current as currentTheme
} from "../../themes";
import timeZoneUtils from "../utils.timeZone";
var toMs = dateUtils.dateToMilliseconds;
var APPOINTMENT_MIN_SIZE = 2;
var APPOINTMENT_DEFAULT_HEIGHT = 20;
var COMPACT_THEME_APPOINTMENT_DEFAULT_HEIGHT = 18;
var DROP_DOWN_BUTTON_ADAPTIVE_SIZE = 28;
class BaseRenderingStrategy {
constructor(instance) {
this.instance = instance;
this._initPositioningStrategy()
}
get isVirtualScrolling() {
return this.instance.fire("isVirtualScrolling")
}
_isAdaptive() {
return this.instance.fire("isAdaptive")
}
_correctCollectorCoordinatesInAdaptive(coordinates, isAllDay) {
coordinates.top = coordinates.top + this.getCollectorTopOffset(isAllDay);
coordinates.left = coordinates.left + this.getCollectorLeftOffset()
}
_initPositioningStrategy() {
this._positioningStrategy = this._isAdaptive() ? new AdaptivePositioningStrategy(this) : new BasePositioningStrategy(this)
}
getPositioningStrategy() {
return this._positioningStrategy
}
getAppointmentMinSize() {
return APPOINTMENT_MIN_SIZE
}
keepAppointmentSettings() {
return false
}
getDeltaTime() {}
getAppointmentGeometry(coordinates) {
return coordinates
}
needCorrectAppointmentDates() {
return true
}
getDirection() {
return "horizontal"
}
createTaskPositionMap(items) {
delete this._maxAppointmentCountPerCell;
var length = items && items.length;
if (!length) {
return
}
this._defaultWidth = this.instance.fire("getCellWidth");
this._defaultHeight = this.instance.fire("getCellHeight");
this._allDayHeight = this.instance._allDayCellHeight;
var map = [];
for (var i = 0; i < length; i++) {
var coordinates = this._getItemPosition(items[i]);
if (this._isRtl()) {
coordinates = this._correctRtlCoordinates(coordinates)
}
map.push(coordinates)
}
var positionArray = this._getSortedPositions(map);
var resultPositions = this._getResultPositions(positionArray);
return this._getExtendedPositionMap(map, resultPositions)
}
_getDeltaWidth(args, initialSize) {
var intervalWidth = this.instance.fire("getResizableStep") || this.getAppointmentMinSize();
var initialWidth = initialSize.width;
return Math.round((args.width - initialWidth) / intervalWidth)
}
_correctRtlCoordinates(coordinates) {
var width = coordinates[0].width || this._getAppointmentMaxWidth();
coordinates.forEach(coordinate => {
if (!coordinate.appointmentReduced) {
coordinate.left -= width
}
});
return coordinates
}
_getAppointmentMaxWidth() {
return this.getDefaultCellWidth()
}
_getItemPosition(appointment) {
var position = this._getAppointmentCoordinates(appointment);
var allDay = this.isAllDay(appointment);
var result = [];
for (var j = 0; j < position.length; j++) {
var height = this.calculateAppointmentHeight(appointment, position[j]);
var width = this.calculateAppointmentWidth(appointment, position[j]);
var resultWidth = width;
var appointmentReduced = null;
var multiWeekAppointmentParts = [];
var initialRowIndex = position[j].rowIndex;
var initialCellIndex = position[j].cellIndex;
if (this._needVerifyItemSize() || allDay) {
var currentMaxAllowedPosition = position[j].hMax;
if (this.isAppointmentGreaterThan(currentMaxAllowedPosition, {
left: position[j].left,
width: width
})) {
appointmentReduced = "head";
initialRowIndex = position[j].rowIndex;
initialCellIndex = position[j].cellIndex;
resultWidth = this._reduceMultiWeekAppointment(width, {
left: position[j].left,
right: currentMaxAllowedPosition
});
multiWeekAppointmentParts = this._getAppointmentParts({
sourceAppointmentWidth: width,
reducedWidth: resultWidth,
height: height
}, position[j]);
if (this._isRtl()) {
position[j].left = currentMaxAllowedPosition
}
}
}
extend(position[j], {
height: height,
width: resultWidth,
allDay: allDay,
rowIndex: initialRowIndex,
cellIndex: initialCellIndex,
appointmentReduced: appointmentReduced
});
result = this._getAppointmentPartsPosition(multiWeekAppointmentParts, position[j], result)
}
return result
}
_getAppointmentPartsPosition(appointmentParts, position, result) {
if (appointmentParts.length) {
appointmentParts.unshift(position);
result = result.concat(appointmentParts)
} else {
result.push(position)
}
return result
}
_getAppointmentCoordinates(appointment) {
return this.instance.fire("createAppointmentSettings", appointment)
}
_isRtl() {
return this.instance.option("rtlEnabled")
}
_getAppointmentParts() {
return []
}
_getCompactAppointmentParts(appointmentWidth) {
var cellWidth = this.getDefaultCellWidth() || this.getAppointmentMinSize();
return Math.round(appointmentWidth / cellWidth)
}
_reduceMultiWeekAppointment(sourceAppointmentWidth, bound) {
if (this._isRtl()) {
sourceAppointmentWidth = Math.floor(bound.left - bound.right)
} else {
sourceAppointmentWidth = bound.right - Math.floor(bound.left)
}
return sourceAppointmentWidth
}
calculateAppointmentHeight() {
return 0
}
calculateAppointmentWidth() {
return 0
}
isAppointmentGreaterThan(etalon, comparisonParameters) {
var result = comparisonParameters.left + comparisonParameters.width - etalon;
if (this._isRtl()) {
result = etalon + comparisonParameters.width - comparisonParameters.left
}
return result > this.getDefaultCellWidth() / 2
}
isAllDay() {
return false
}
cropAppointmentWidth(width, cellWidth) {
if (this.instance.fire("isGroupedByDate")) {
width = cellWidth
}
return width
}
_getSortedPositions(positionList) {
var result = [];
var round = value => Math.round(100 * value) / 100;
var createItem = (rowIndex, cellIndex, top, left, bottom, right, position, allDay) => ({
i: rowIndex,
j: cellIndex,
top: round(top),
left: round(left),
bottom: round(bottom),
right: round(right),
cellPosition: position,
allDay: allDay
});
for (var rowIndex = 0, rowCount = positionList.length; rowIndex < rowCount; rowIndex++) {
for (var cellIndex = 0, cellCount = positionList[rowIndex].length; cellIndex < cellCount; cellIndex++) {
var {
top: top,
left: left,
height: height,
width: width,
cellPosition: cellPosition,
allDay: allDay
} = positionList[rowIndex][cellIndex];
result.push(createItem(rowIndex, cellIndex, top, left, top + height, left + width, cellPosition, allDay))
}
}
return result.sort((a, b) => this._sortCondition(a, b))
}
_sortCondition() {}
_getConditions(a, b) {
var isSomeEdge = this._isSomeEdge(a, b);
return {
columnCondition: isSomeEdge || this._normalizeCondition(a.left, b.left),
rowCondition: isSomeEdge || this._normalizeCondition(a.top, b.top),
cellPositionCondition: isSomeEdge || this._normalizeCondition(a.cellPosition, b.cellPosition)
}
}
_rowCondition(a, b) {
var conditions = this._getConditions(a, b);
return conditions.columnCondition || conditions.rowCondition
}
_columnCondition(a, b) {
var conditions = this._getConditions(a, b);
return conditions.rowCondition || conditions.columnCondition
}
_isSomeEdge(a, b) {
return a.i === b.i && a.j === b.j
}
_normalizeCondition(first, second) {
var result = first - second;
return Math.abs(result) > 1 ? result : 0
}
_isItemsCross(firstItem, secondItem) {
var areItemsInTheSameTable = !!firstItem.allDay === !!secondItem.allDay;
var areItemsAllDay = firstItem.allDay && secondItem.allDay;
if (areItemsInTheSameTable) {
var orientation = this._getOrientation(areItemsAllDay);
return this._checkItemsCrossing(firstItem, secondItem, orientation)
} else {
return false
}
}
_checkItemsCrossing(firstItem, secondItem, orientation) {
var firstItemSide_1 = Math.floor(firstItem[orientation[0]]);
var firstItemSide_2 = Math.floor(firstItem[orientation[1]]);
var secondItemSide_1 = Math.ceil(secondItem[orientation[0]]);
var secondItemSide_2 = Math.ceil(secondItem[orientation[1]]);
var isItemCross = Math.abs(firstItem[orientation[2]] - secondItem[orientation[2]]) <= 1;
return isItemCross && (firstItemSide_1 <= secondItemSide_1 && firstItemSide_2 > secondItemSide_1 || firstItemSide_1 < secondItemSide_2 && firstItemSide_2 >= secondItemSide_2 || firstItemSide_1 === secondItemSide_1 && firstItemSide_2 === secondItemSide_2)
}
_getOrientation(isAllDay) {
return isAllDay ? ["left", "right", "top"] : ["top", "bottom", "left"]
}
_getResultPositions(sortedArray) {
var result = [];
var i;
var sortedIndex = 0;
var currentItem;
var indexes;
var itemIndex;
var maxIndexInStack = 0;
var stack = {};
var findFreeIndex = (indexes, index) => {
var isFind = indexes.some(item => item === index);
if (isFind) {
return findFreeIndex(indexes, ++index)
} else {
return index
}
};
var createItem = (currentItem, index) => {
var currentIndex = index || 0;
return {
index: currentIndex,
i: currentItem.i,
j: currentItem.j,
left: currentItem.left,
right: currentItem.right,
top: currentItem.top,
bottom: currentItem.bottom,
allDay: currentItem.allDay,
sortedIndex: this._skipSortedIndex(currentIndex) ? null : sortedIndex++
}
};
var startNewStack = currentItem => {
stack.items = [createItem(currentItem)];
stack.left = currentItem.left;
stack.right = currentItem.right;
stack.top = currentItem.top;
stack.bottom = currentItem.bottom;
stack.allDay = currentItem.allDay
};
var pushItemsInResult = items => {
items.forEach(item => {
result.push({
index: item.index,
count: maxIndexInStack + 1,
i: item.i,
j: item.j,
sortedIndex: item.sortedIndex
})
})
};
for (i = 0; i < sortedArray.length; i++) {
currentItem = sortedArray[i];
indexes = [];
if (!stack.items) {
startNewStack(currentItem)
} else if (this._isItemsCross(stack, currentItem)) {
stack.items.forEach((item, index) => {
if (this._isItemsCross(item, currentItem)) {
indexes.push(item.index)
}
});
itemIndex = indexes.length ? findFreeIndex(indexes, 0) : 0;
stack.items.push(createItem(currentItem, itemIndex));
maxIndexInStack = Math.max(itemIndex, maxIndexInStack);
stack.left = Math.min(stack.left, currentItem.left);
stack.right = Math.max(stack.right, currentItem.right);
stack.top = Math.min(stack.top, currentItem.top);
stack.bottom = Math.max(stack.bottom, currentItem.bottom);
stack.allDay = currentItem.allDay
} else {
pushItemsInResult(stack.items);
stack = {};
startNewStack(currentItem);
maxIndexInStack = 0
}
}
if (stack.items) {
pushItemsInResult(stack.items)
}
return result.sort((function(a, b) {
var columnCondition = a.j - b.j;
var rowCondition = a.i - b.i;
return rowCondition ? rowCondition : columnCondition
}))
}
_skipSortedIndex(index) {
return index > this._getMaxAppointmentCountPerCell() - 1
}
_findIndexByKey(arr, iKey, jKey, iValue, jValue) {
var result = 0;
for (var i = 0, len = arr.length; i < len; i++) {
if (arr[i][iKey] === iValue && arr[i][jKey] === jValue) {
result = i;
break
}
}
return result
}
_getExtendedPositionMap(map, positions) {
var positionCounter = 0;
var result = [];
for (var i = 0, mapLength = map.length; i < mapLength; i++) {
var resultString = [];
for (var j = 0, itemLength = map[i].length; j < itemLength; j++) {
map[i][j].index = positions[positionCounter].index;
map[i][j].sortedIndex = positions[positionCounter].sortedIndex;
map[i][j].count = positions[positionCounter++].count;
resultString.push(map[i][j]);
this._checkLongCompactAppointment(map[i][j], resultString)
}
result.push(resultString)
}
return result
}
_checkLongCompactAppointment(item, result) {
this._splitLongCompactAppointment(item, result);
return result
}
_splitLongCompactAppointment(item, result) {
var appointmentCountPerCell = this._getMaxAppointmentCountPerCellByType(item.allDay);
var compactCount = 0;
if (void 0 !== appointmentCountPerCell && item.index > appointmentCountPerCell - 1) {
item.isCompact = true;
compactCount = this._getCompactAppointmentParts(item.width);
for (var k = 1; k < compactCount; k++) {
var compactPart = extend(true, {}, item);
compactPart.left = this._getCompactLeftCoordinate(item.left, k);
compactPart.cellIndex = compactPart.cellIndex + k;
compactPart.sortedIndex = null;
result.push(compactPart)
}
}
return result
}
normalizeEndDateByViewEnd(appointment, endDate) {
var result = new Date(endDate.getTime());
if (!this.isAllDay(appointment)) {
var viewEndDate = dateUtils.roundToHour(this.instance.fire("getEndViewDate"));
if (result > viewEndDate) {
result = viewEndDate
}
}
var endDayHour = this.instance._getCurrentViewOption("endDayHour");
var allDay = this.instance.fire("getField", "allDay", appointment);
var currentViewEndTime = new Date(new Date(endDate.getTime()).setHours(endDayHour, 0, 0, 0));
if (result.getTime() > currentViewEndTime.getTime() || allDay && result.getHours() < endDayHour) {
result = currentViewEndTime
}
return result
}
_adjustDurationByDaylightDiff(duration, startDate, endDate) {
var daylightDiff = timeZoneUtils.getDaylightOffset(startDate, endDate);
return this._needAdjustDuration(daylightDiff) ? this._calculateDurationByDaylightDiff(duration, daylightDiff) : duration
}
_needAdjustDuration(diff) {
return 0 !== diff
}
_calculateDurationByDaylightDiff(duration, diff) {
return duration + diff * toMs("minute")
}
_getAppointmentDurationInMs(startDate, endDate, allDay) {
return this.instance.fire("getAppointmentDurationInMs", {
startDate: startDate,
endDate: endDate,
allDay: allDay
})
}
_markAppointmentAsVirtual(coordinates) {
var isAllDay = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : false;
var countFullWidthAppointmentInCell = this._getMaxAppointmentCountPerCellByType(isAllDay);
if (coordinates.count - countFullWidthAppointmentInCell > 0) {
var {
top: top,
left: left
} = coordinates;
coordinates.virtual = {
top: top,
left: left,
index: this._generateAppointmentCollectorIndex(coordinates, isAllDay),
isAllDay: isAllDay
}
}
}
_generateAppointmentCollectorIndex(_ref, isAllDay) {
var {
groupIndex: groupIndex,
rowIndex: rowIndex,
cellIndex: cellIndex
} = _ref;
return "".concat(groupIndex, "-").concat(rowIndex, "-").concat(cellIndex, "-").concat(isAllDay)
}
_getMaxAppointmentCountPerCellByType(isAllDay) {
var appointmentCountPerCell = this._getMaxAppointmentCountPerCell();
if (isObject(appointmentCountPerCell)) {
return isAllDay ? this._getMaxAppointmentCountPerCell().allDay : this._getMaxAppointmentCountPerCell().simple
} else {
return appointmentCountPerCell
}
}
getDropDownAppointmentWidth(intervalCount, isAllDay) {
return this.getPositioningStrategy().getDropDownAppointmentWidth(intervalCount, isAllDay)
}
getDropDownAppointmentHeight() {
return this.getPositioningStrategy().getDropDownAppointmentHeight()
}
getDropDownButtonAdaptiveSize() {
return DROP_DOWN_BUTTON_ADAPTIVE_SIZE
}
getDefaultCellWidth() {
return this._defaultWidth
}
getDefaultCellHeight() {
return this._defaultHeight
}
getDefaultAllDayCellHeight() {
return this._allDayHeight
}
getCollectorTopOffset(allDay) {
return this.getPositioningStrategy().getCollectorTopOffset(allDay)
}
getCollectorLeftOffset() {
return this.getPositioningStrategy().getCollectorLeftOffset()
}
getAppointmentDataCalculator() {}
_customizeCoordinates(coordinates, height, appointmentCountPerCell, topOffset, isAllDay) {
var index = coordinates.index;
var appointmentHeight = height / appointmentCountPerCell;
var appointmentTop = coordinates.top + index * appointmentHeight;
var top = appointmentTop + topOffset;
var width = coordinates.width;
var left = coordinates.left;
if (coordinates.isCompact) {
this._isAdaptive() && this._correctCollectorCoordinatesInAdaptive(coordinates, isAllDay);
this._markAppointmentAsVirtual(coordinates, isAllDay)
}
return {
height: appointmentHeight,
width: width,
top: top,
left: left,
empty: this._isAppointmentEmpty(height, width)
}
}
_isAppointmentEmpty(height, width) {
return height < this._getAppointmentMinHeight() || width < this._getAppointmentMinWidth()
}
_calculateGeometryConfig(coordinates) {
var overlappingMode = this.instance.fire("getMaxAppointmentsPerCell");
var offsets = this._getOffsets();
var appointmentDefaultOffset = this._getAppointmentDefaultOffset();
var appointmentCountPerCell = this._getAppointmentCount(overlappingMode, coordinates);
var ratio = this._getDefaultRatio(coordinates, appointmentCountPerCell);
var maxHeight = this._getMaxHeight();
if (!isNumeric(appointmentCountPerCell)) {
appointmentCountPerCell = coordinates.count;
ratio = (maxHeight - offsets.unlimited) / maxHeight
}
var topOffset = (1 - ratio) * maxHeight;
if ("auto" === overlappingMode || isNumeric(overlappingMode)) {
ratio = 1;
maxHeight -= appointmentDefaultOffset;
topOffset = appointmentDefaultOffset
}
return {
height: ratio * maxHeight,
appointmentCountPerCell: appointmentCountPerCell,
offset: topOffset
}
}
_getAppointmentCount() {}
_getDefaultRatio() {}
_getOffsets() {}
_getMaxHeight() {}
_needVerifyItemSize() {
return false
}
needSeparateAppointment(allDay) {
return this.instance.fire("isGroupedByDate") && allDay
}
_getMaxAppointmentCountPerCell() {
if (!this._maxAppointmentCountPerCell) {
var overlappingMode = this.instance.fire("getMaxAppointmentsPerCell");
var appointmentCountPerCell;
if (isNumeric(overlappingMode)) {
appointmentCountPerCell = overlappingMode
}
if ("auto" === overlappingMode) {
appointmentCountPerCell = this._getDynamicAppointmentCountPerCell()
}
if ("unlimited" === overlappingMode) {
appointmentCountPerCell = void 0
}
this._maxAppointmentCountPerCell = appointmentCountPerCell
}
return this._maxAppointmentCountPerCell
}
_getDynamicAppointmentCountPerCell() {
return this.getPositioningStrategy().getDynamicAppointmentCountPerCell()
}
hasAllDayAppointments() {
return false
}
_isCompactTheme() {
return "compact" === (currentTheme() || "").split(".").pop()
}
_getAppointmentDefaultOffset() {
return this.getPositioningStrategy().getAppointmentDefaultOffset()
}
_getAppointmentDefaultHeight() {
return this._getAppointmentHeightByTheme()
}
_getAppointmentMinHeight() {
return this._getAppointmentDefaultHeight()
}
_getAppointmentHeightByTheme() {
return this._isCompactTheme() ? COMPACT_THEME_APPOINTMENT_DEFAULT_HEIGHT : APPOINTMENT_DEFAULT_HEIGHT
}
_getAppointmentDefaultWidth() {
return this.getPositioningStrategy()._getAppointmentDefaultWidth()
}
_getAppointmentMinWidth() {
return this._getAppointmentDefaultWidth()
}
_needVerticalGroupBounds() {
return false
}
_needHorizontalGroupBounds() {
return false
}
}
export default BaseRenderingStrategy;