devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
551 lines (550 loc) • 23.6 kB
JavaScript
/**
* DevExtreme (ui/scheduler/ui.scheduler.appointments.strategy.base.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var noop = require("../../core/utils/common").noop,
Class = require("../../core/class"),
extend = require("../../core/utils/extend").extend,
errors = require("../widget/ui.errors"),
dateUtils = require("../../core/utils/date"),
isNumeric = require("../../core/utils/type").isNumeric,
themes = require("../themes");
var toMs = dateUtils.dateToMilliseconds;
var abstract = Class.abstract;
var APPOINTMENT_MIN_SIZE = 2,
COMPACT_APPOINTMENT_DEFAULT_SIZE = 15,
APPOINTMENT_DEFAULT_HEIGHT = 20,
APPOINTMENT_DEFAULT_WIDTH = 40,
COMPACT_THEME_APPOINTMENT_DEFAULT_HEIGHT = 18,
COMPACT_THEME_APPOINTMENT_DEFAULT_OFFSET = 22,
COMPACT_APPOINTMENT_DEFAULT_OFFSET = 3;
var BaseRenderingStrategy = Class.inherit({
ctor: function(instance) {
this.instance = instance
},
getAppointmentMinSize: function() {
return APPOINTMENT_MIN_SIZE
},
keepAppointmentSettings: function() {
return false
},
getDeltaTime: abstract,
getAppointmentGeometry: function(coordinates) {
return coordinates
},
needCorrectAppointmentDates: function() {
return true
},
getDirection: function() {
return "horizontal"
},
createTaskPositionMap: function(items) {
delete this._maxAppointmentCountPerCell;
var length = items && items.length;
if (!length) {
return
}
this._defaultWidth = this.instance._cellWidth;
this._defaultHeight = this.instance._cellHeight;
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),
resultPositions = this._getResultPositions(positionArray);
return this._getExtendedPositionMap(map, resultPositions)
},
_getDeltaWidth: function(args, initialSize) {
var cellWidth = this._defaultWidth || this.getAppointmentMinSize(),
initialWidth = initialSize.width;
return Math.round((args.width - initialWidth) / cellWidth)
},
_correctRtlCoordinates: function(coordinates) {
var width = coordinates[0].width || this._getAppointmentMaxWidth();
if (!coordinates[0].appointmentReduced) {
coordinates[0].left -= width
}
this._correctRtlCoordinatesParts(coordinates, width);
return coordinates
},
_correctRtlCoordinatesParts: noop,
_getAppointmentMaxWidth: function() {
return this._defaultWidth
},
_getItemPosition: function(item) {
var position = this._getAppointmentCoordinates(item),
allDay = this.isAllDay(item),
result = [],
startDate = new Date(this.instance.fire("getField", "startDate", item)),
isRecurring = !!item.recurrenceRule;
for (var j = 0; j < position.length; j++) {
var height = this.calculateAppointmentHeight(item, position[j]),
width = this.calculateAppointmentWidth(item, position[j], isRecurring),
resultWidth = width,
appointmentReduced = null,
multiWeekAppointmentParts = [],
initialRowIndex = position[j].rowIndex,
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], startDate);
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: function(appointmentParts, position, result) {
if (appointmentParts.length) {
appointmentParts.unshift(position);
result = result.concat(appointmentParts)
} else {
result.push(position)
}
return result
},
_getAppointmentCoordinates: function(itemData) {
var coordinates = [{
top: 0,
left: 0
}];
this.instance.fire("needCoordinates", {
startDate: this._startDate(itemData),
originalStartDate: this._startDate(itemData, true),
appointmentData: itemData,
callback: function(value) {
coordinates = value
}
});
return coordinates
},
_isRtl: function() {
return this.instance.option("rtlEnabled")
},
_getAppointmentParts: function() {
return []
},
_getCompactAppointmentParts: function(appointmentWidth) {
var cellWidth = this._defaultWidth || this.getAppointmentMinSize();
return Math.round(appointmentWidth / cellWidth)
},
_reduceMultiWeekAppointment: function(sourceAppointmentWidth, bound) {
if (this._isRtl()) {
sourceAppointmentWidth = Math.floor(bound.left - bound.right)
} else {
sourceAppointmentWidth = bound.right - Math.floor(bound.left)
}
return sourceAppointmentWidth
},
calculateAppointmentHeight: function() {
return 0
},
calculateAppointmentWidth: function() {
return 0
},
isAppointmentGreaterThan: function(etalon, comparisonParameters) {
var result = comparisonParameters.left + comparisonParameters.width - etalon;
if (this._isRtl()) {
result = etalon + comparisonParameters.width - comparisonParameters.left
}
return result > this._defaultWidth / 2
},
isAllDay: function() {
return false
},
_getSortedPositions: function(arr) {
var result = [],
__tmpIndex = 0;
for (var i = 0, arrLength = arr.length; i < arrLength; i++) {
for (var j = 0, itemLength = arr[i].length; j < itemLength; j++) {
var item = arr[i][j];
var start = {
i: i,
j: j,
top: item.top,
left: item.left,
isStart: true,
allDay: item.allDay,
__tmpIndex: __tmpIndex
};
__tmpIndex++;
var end = {
i: i,
j: j,
top: item.top + item.height,
left: item.left + item.width,
isStart: false,
allDay: item.allDay,
__tmpIndex: __tmpIndex
};
result.push(start, end);
__tmpIndex++
}
}
result.sort(function(a, b) {
return this._sortCondition(a, b)
}.bind(this));
return result
},
_fixUnstableSorting: function(comparisonResult, a, b) {
if (0 === comparisonResult) {
if (a.__tmpIndex < b.__tmpIndex) {
return -1
}
if (a.__tmpIndex > b.__tmpIndex) {
return 1
}
}
return comparisonResult
},
_sortCondition: abstract,
_rowCondition: function(a, b) {
var columnCondition = this._normalizeCondition(a.left, b.left),
rowCondition = this._normalizeCondition(a.top, b.top);
return columnCondition ? columnCondition : rowCondition ? rowCondition : a.isStart - b.isStart
},
_columnCondition: function(a, b) {
var columnCondition = this._normalizeCondition(a.left, b.left),
rowCondition = this._normalizeCondition(a.top, b.top);
return rowCondition ? rowCondition : columnCondition ? columnCondition : a.isStart - b.isStart
},
_normalizeCondition: function(first, second) {
var result = first - second;
return Math.abs(result) > 1.001 ? result : 0
},
_getResultPositions: function(sortedArray) {
var position, stack = [],
indexes = [],
result = [],
intersectPositions = [],
intersectPositionCount = 0,
sortedIndex = 0;
for (var i = 0; i < sortedArray.length; i++) {
var j, current = sortedArray[i];
if (current.isStart) {
position = void 0;
for (j = 0; j < indexes.length; j++) {
if (!indexes[j]) {
position = j;
indexes[j] = true;
break
}
}
if (void 0 === position) {
position = indexes.length;
indexes.push(true);
for (j = 0; j < stack.length; j++) {
stack[j].count++
}
}
stack.push({
index: position,
count: indexes.length,
i: current.i,
j: current.j,
sortedIndex: this._skipSortedIndex(position) ? null : sortedIndex++
});
if (intersectPositionCount < indexes.length) {
intersectPositionCount = indexes.length
}
} else {
var removeIndex = this._findIndexByKey(stack, "i", "j", current.i, current.j),
resultItem = stack[removeIndex];
stack.splice(removeIndex, 1);
indexes[resultItem.index] = false;
intersectPositions.push(resultItem);
if (!stack.length) {
indexes = [];
for (var k = 0; k < intersectPositions.length; k++) {
intersectPositions[k].count = intersectPositionCount
}
intersectPositions = [];
intersectPositionCount = 0
}
result.push(resultItem)
}
}
return result.sort(function(a, b) {
var columnCondition = a.j - b.j,
rowCondition = a.i - b.i;
return rowCondition ? rowCondition : columnCondition
})
},
_skipSortedIndex: function(index) {
return this.instance.fire("getMaxAppointmentsPerCell") && index > this._getMaxAppointmentCountPerCell() - 1
},
_findIndexByKey: function(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: function(map, positions) {
var positionCounter = 0,
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: noop,
_splitLongCompactAppointment: function(item, result) {
var appointmentCountPerCell = this._getMaxAppointmentCountPerCell();
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
},
_startDate: function(appointment, skipNormalize, position) {
var startDate = position && position.startDate,
viewStartDate = this.instance._getStartDate(appointment, skipNormalize),
text = this.instance.fire("getField", "text", appointment);
if (startDate && viewStartDate > startDate || !startDate) {
startDate = viewStartDate
}
if (isNaN(startDate.getTime())) {
throw errors.Error("E1032", text)
}
return startDate
},
_endDate: function(appointment, position, isRecurring) {
var endDate = this.instance._getEndDate(appointment),
realStartDate = this._startDate(appointment, true),
viewStartDate = this._startDate(appointment, false, position);
endDate = this._checkWrongEndDate(appointment, realStartDate, endDate);
if (viewStartDate.getTime() >= endDate.getTime() || isRecurring) {
var recurrencePartStartDate = position ? position.startDate : realStartDate,
fullDuration = endDate.getTime() - realStartDate.getTime();
fullDuration = this._adjustDurationByDaylightDiff(fullDuration, realStartDate, endDate);
endDate = new Date((viewStartDate.getTime() >= recurrencePartStartDate.getTime() ? recurrencePartStartDate.getTime() : viewStartDate.getTime()) + fullDuration);
if (!dateUtils.sameDate(realStartDate, endDate) && recurrencePartStartDate.getTime() < viewStartDate.getTime()) {
var headDuration = dateUtils.trimTime(endDate).getTime() - recurrencePartStartDate.getTime(),
tailDuration = fullDuration - headDuration || fullDuration;
endDate = new Date(dateUtils.trimTime(viewStartDate).getTime() + tailDuration)
}
}
if (!this.isAllDay(appointment)) {
var viewEndDate = dateUtils.roundToHour(this.instance.fire("getEndViewDate"));
if (endDate > viewEndDate) {
endDate = viewEndDate
}
}
return endDate
},
_adjustDurationByDaylightDiff: function(duration, startDate, endDate) {
var daylightDiff = this.instance.fire("getDaylightOffset", startDate, endDate);
if (0 !== daylightDiff) {
duration += daylightDiff * toMs("minute")
}
return duration
},
_checkWrongEndDate: function(appointment, startDate, endDate) {
if (!endDate || startDate.getTime() >= endDate.getTime()) {
endDate = new Date(startDate.getTime() + 6e4 * this.instance.getAppointmentDurationInMinutes());
this.instance.fire("setField", "endDate", appointment, endDate)
}
return endDate
},
_getAppointmentDurationInMs: function(startDate, endDate, allDay) {
var result;
this.instance.fire("getAppointmentDurationInMs", {
startDate: startDate,
endDate: endDate,
allDay: allDay,
callback: function(duration) {
result = duration
}
});
return result
},
_getMaxNeighborAppointmentCount: function() {
var overlappingMode = this.instance.fire("getMaxAppointmentsPerCell");
if (!overlappingMode) {
var outerAppointmentWidth = this.getCompactAppointmentDefaultSize() + this.getCompactAppointmentDefaultOffset();
return Math.floor(this.getCompactAppointmentGroupMaxWidth() / outerAppointmentWidth)
} else {
return 0
}
},
_markAppointmentAsVirtual: function(coordinates, isAllDay) {
var countFullWidthAppointmentInCell = this._getMaxAppointmentCountPerCell();
if (coordinates.count - countFullWidthAppointmentInCell > this._getMaxNeighborAppointmentCount()) {
coordinates.virtual = {
top: coordinates.top,
left: coordinates.left,
index: coordinates.groupIndex + "-" + coordinates.rowIndex + "-" + coordinates.cellIndex,
isAllDay: isAllDay
}
}
},
getCompactAppointmentGroupMaxWidth: function() {
var widthInPercents = 75;
return widthInPercents * this.getDefaultCellWidth() / 100
},
getDefaultCellWidth: function() {
return this._defaultWidth
},
getCompactAppointmentDefaultSize: function() {
return COMPACT_APPOINTMENT_DEFAULT_SIZE
},
getCompactAppointmentDefaultOffset: function() {
return COMPACT_APPOINTMENT_DEFAULT_OFFSET
},
getAppointmentDataCalculator: noop,
_customizeCoordinates: function(coordinates, height, appointmentCountPerCell, topOffset, isAllDay) {
var compactAppointmentDefaultSize, compactAppointmentDefaultOffset, index = coordinates.index,
appointmentHeight = height / appointmentCountPerCell,
appointmentTop = coordinates.top + index * appointmentHeight,
top = appointmentTop + topOffset,
width = coordinates.width,
left = coordinates.left;
if (coordinates.isCompact) {
compactAppointmentDefaultSize = this.getCompactAppointmentDefaultSize();
compactAppointmentDefaultOffset = this.getCompactAppointmentDefaultOffset();
top = coordinates.top + compactAppointmentDefaultOffset;
left = coordinates.left + (index - appointmentCountPerCell) * (compactAppointmentDefaultSize + compactAppointmentDefaultOffset) + compactAppointmentDefaultOffset;
appointmentHeight = compactAppointmentDefaultSize;
width = compactAppointmentDefaultSize;
this._markAppointmentAsVirtual(coordinates, isAllDay)
}
return {
height: appointmentHeight,
width: width,
top: top,
left: left,
empty: this._isAppointmentEmpty(height, width)
}
},
_isAppointmentEmpty: function(height, width) {
return height < this._getAppointmentDefaultHeight() || width < this._getAppointmentDefaultWidth()
},
_calculateGeometryConfig: function(coordinates) {
var overlappingMode = this.instance.fire("getMaxAppointmentsPerCell"),
offsets = this._getOffsets(),
appointmentDefaultOffset = this._getAppointmentDefaultOffset();
var appointmentCountPerCell = this._getAppointmentCount(overlappingMode, coordinates);
var ratio = this._getDefaultRatio(coordinates, appointmentCountPerCell);
var maxHeight = this._getMaxHeight();
if (!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: noop,
_getDefaultRatio: noop,
_getOffsets: noop,
_getMaxHeight: noop,
_needVerifyItemSize: function() {
return false
},
_getMaxAppointmentCountPerCell: function() {
if (!this._maxAppointmentCountPerCell) {
var appointmentCountPerCell, overlappingMode = this.instance.fire("getMaxAppointmentsPerCell");
if (!overlappingMode) {
appointmentCountPerCell = 2
}
if (isNumeric(overlappingMode)) {
appointmentCountPerCell = overlappingMode
}
if ("auto" === overlappingMode) {
appointmentCountPerCell = this._getDynamicAppointmentCountPerCell()
}
if ("unlimited" === overlappingMode) {
appointmentCountPerCell = void 0
}
this._maxAppointmentCountPerCell = appointmentCountPerCell
}
return this._maxAppointmentCountPerCell
},
_getDynamicAppointmentCountPerCell: function() {
var cellHeight = this.instance.fire("getCellHeight");
return Math.floor((cellHeight - this._getAppointmentDefaultOffset()) / this._getAppointmentDefaultHeight())
},
_isCompactTheme: function() {
return "compact" === (themes.current() || "").split(".")[2]
},
_getAppointmentDefaultOffset: function() {
return this._isCompactTheme() ? COMPACT_THEME_APPOINTMENT_DEFAULT_OFFSET : this.instance.option("_appointmentOffset")
},
_getAppointmentDefaultHeight: function() {
return this._isCompactTheme() ? COMPACT_THEME_APPOINTMENT_DEFAULT_HEIGHT : APPOINTMENT_DEFAULT_HEIGHT
},
_getAppointmentDefaultWidth: function() {
return APPOINTMENT_DEFAULT_WIDTH
},
_needVerticalGroupBounds: function() {
return false
},
_needHorizontalGroupBounds: function() {
return false
}
});
module.exports = BaseRenderingStrategy;