devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
687 lines (543 loc) • 24.3 kB
JavaScript
"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 ctor(instance) {
this.instance = instance;
},
getAppointmentMinSize: function getAppointmentMinSize() {
return APPOINTMENT_MIN_SIZE;
},
keepAppointmentSettings: function keepAppointmentSettings() {
return false;
},
getDeltaTime: abstract,
getAppointmentGeometry: function getAppointmentGeometry(coordinates) {
return coordinates;
},
needCorrectAppointmentDates: function needCorrectAppointmentDates() {
return true;
},
getDirection: function getDirection() {
return "horizontal";
},
createTaskPositionMap: function createTaskPositionMap(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 _getDeltaWidth(args, initialSize) {
var cellWidth = this._defaultWidth || this.getAppointmentMinSize(),
initialWidth = initialSize.width;
return Math.round((args.width - initialWidth) / cellWidth);
},
_correctRtlCoordinates: function _correctRtlCoordinates(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 _getAppointmentMaxWidth() {
return this._defaultWidth;
},
_getItemPosition: function _getItemPosition(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 _getAppointmentPartsPosition(appointmentParts, position, result) {
if (appointmentParts.length) {
appointmentParts.unshift(position);
result = result.concat(appointmentParts);
} else {
result.push(position);
}
return result;
},
_getAppointmentCoordinates: function _getAppointmentCoordinates(itemData) {
var coordinates = [{
top: 0,
left: 0
}];
this.instance.fire("needCoordinates", {
startDate: this._startDate(itemData),
originalStartDate: this._startDate(itemData, true),
appointmentData: itemData,
callback: function callback(value) {
coordinates = value;
}
});
return coordinates;
},
_isRtl: function _isRtl() {
return this.instance.option("rtlEnabled");
},
_getAppointmentParts: function _getAppointmentParts() {
return [];
},
_getCompactAppointmentParts: function _getCompactAppointmentParts(appointmentWidth) {
var cellWidth = this._defaultWidth || this.getAppointmentMinSize();
return Math.round(appointmentWidth / cellWidth);
},
_reduceMultiWeekAppointment: function _reduceMultiWeekAppointment(sourceAppointmentWidth, bound) {
if (this._isRtl()) {
sourceAppointmentWidth = Math.floor(bound.left - bound.right);
} else {
sourceAppointmentWidth = bound.right - Math.floor(bound.left);
}
return sourceAppointmentWidth;
},
calculateAppointmentHeight: function calculateAppointmentHeight() {
return 0;
},
calculateAppointmentWidth: function calculateAppointmentWidth() {
return 0;
},
isAppointmentGreaterThan: function isAppointmentGreaterThan(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 isAllDay() {
return false;
},
_getSortedPositions: function _getSortedPositions(arr) {
var result = [],
// unstable sorting fix
__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 _fixUnstableSorting(comparisonResult, a, b) {
if (comparisonResult === 0) {
if (a.__tmpIndex < b.__tmpIndex) return -1;
if (a.__tmpIndex > b.__tmpIndex) return 1;
}
return comparisonResult;
},
_sortCondition: abstract,
_rowCondition: function _rowCondition(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 _columnCondition(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 _normalizeCondition(first, second) {
// NOTE: ie & ff pixels
var result = first - second;
return Math.abs(result) > 1.001 ? result : 0;
},
_getResultPositions: function _getResultPositions(sortedArray) {
var stack = [],
indexes = [],
result = [],
intersectPositions = [],
intersectPositionCount = 0,
sortedIndex = 0,
position;
for (var i = 0; i < sortedArray.length; i++) {
var current = sortedArray[i],
j;
if (current.isStart) {
position = undefined;
for (j = 0; j < indexes.length; j++) {
if (!indexes[j]) {
position = j;
indexes[j] = true;
break;
}
}
if (position === undefined) {
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 _skipSortedIndex(index) {
return this.instance.fire("getMaxAppointmentsPerCell") && index > this._getMaxAppointmentCountPerCell() - 1;
},
_findIndexByKey: function _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: function _getExtendedPositionMap(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 _splitLongCompactAppointment(item, result) {
var appointmentCountPerCell = this._getMaxAppointmentCountPerCell();
var compactCount = 0;
if (appointmentCountPerCell !== undefined && 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 _startDate(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 _endDate(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 _adjustDurationByDaylightDiff(duration, startDate, endDate) {
var daylightDiff = this.instance.fire("getDaylightOffset", startDate, endDate);
if (daylightDiff !== 0) {
duration += daylightDiff * toMs("minute");
}
return duration;
},
_checkWrongEndDate: function _checkWrongEndDate(appointment, startDate, endDate) {
if (!endDate || startDate.getTime() >= endDate.getTime()) {
endDate = new Date(startDate.getTime() + this.instance.getAppointmentDurationInMinutes() * 60000);
this.instance.fire("setField", "endDate", appointment, endDate);
}
return endDate;
},
_getAppointmentDurationInMs: function _getAppointmentDurationInMs(startDate, endDate, allDay) {
var result;
this.instance.fire("getAppointmentDurationInMs", {
startDate: startDate,
endDate: endDate,
allDay: allDay,
callback: function callback(duration) {
result = duration;
}
});
return result;
},
_getMaxNeighborAppointmentCount: function _getMaxNeighborAppointmentCount() {
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 _markAppointmentAsVirtual(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 getCompactAppointmentGroupMaxWidth() {
var widthInPercents = 75;
return widthInPercents * this.getDefaultCellWidth() / 100;
},
getDefaultCellWidth: function getDefaultCellWidth() {
return this._defaultWidth;
},
getCompactAppointmentDefaultSize: function getCompactAppointmentDefaultSize() {
return COMPACT_APPOINTMENT_DEFAULT_SIZE;
},
getCompactAppointmentDefaultOffset: function getCompactAppointmentDefaultOffset() {
return COMPACT_APPOINTMENT_DEFAULT_OFFSET;
},
getAppointmentDataCalculator: noop,
_customizeCoordinates: function _customizeCoordinates(coordinates, height, appointmentCountPerCell, topOffset, isAllDay) {
var index = coordinates.index,
appointmentHeight = height / appointmentCountPerCell,
appointmentTop = coordinates.top + index * appointmentHeight,
top = appointmentTop + topOffset,
width = coordinates.width,
left = coordinates.left,
compactAppointmentDefaultSize,
compactAppointmentDefaultOffset;
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 _isAppointmentEmpty(height, width) {
return height < this._getAppointmentDefaultHeight() || width < this._getAppointmentDefaultWidth();
},
_calculateGeometryConfig: function _calculateGeometryConfig(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 (overlappingMode === "auto" || isNumeric(overlappingMode)) {
ratio = 1;
maxHeight = maxHeight - appointmentDefaultOffset;
topOffset = appointmentDefaultOffset;
}
return {
height: ratio * maxHeight,
appointmentCountPerCell: appointmentCountPerCell,
offset: topOffset
};
},
_getAppointmentCount: noop,
_getDefaultRatio: noop,
_getOffsets: noop,
_getMaxHeight: noop,
_needVerifyItemSize: function _needVerifyItemSize() {
return false;
},
_getMaxAppointmentCountPerCell: function _getMaxAppointmentCountPerCell() {
if (!this._maxAppointmentCountPerCell) {
var overlappingMode = this.instance.fire("getMaxAppointmentsPerCell"),
appointmentCountPerCell;
if (!overlappingMode) {
appointmentCountPerCell = 2;
}
if (isNumeric(overlappingMode)) {
appointmentCountPerCell = overlappingMode;
}
if (overlappingMode === "auto") {
appointmentCountPerCell = this._getDynamicAppointmentCountPerCell();
}
if (overlappingMode === "unlimited") {
appointmentCountPerCell = undefined;
}
this._maxAppointmentCountPerCell = appointmentCountPerCell;
}
return this._maxAppointmentCountPerCell;
},
_getDynamicAppointmentCountPerCell: function _getDynamicAppointmentCountPerCell() {
var cellHeight = this.instance.fire("getCellHeight");
return Math.floor((cellHeight - this._getAppointmentDefaultOffset()) / this._getAppointmentDefaultHeight());
},
_isCompactTheme: function _isCompactTheme() {
return (themes.current() || "").split(".")[2] === "compact";
},
_getAppointmentDefaultOffset: function _getAppointmentDefaultOffset() {
return this._isCompactTheme() ? COMPACT_THEME_APPOINTMENT_DEFAULT_OFFSET : this.instance.option("_appointmentOffset");
},
_getAppointmentDefaultHeight: function _getAppointmentDefaultHeight() {
return this._isCompactTheme() ? COMPACT_THEME_APPOINTMENT_DEFAULT_HEIGHT : APPOINTMENT_DEFAULT_HEIGHT;
},
_getAppointmentDefaultWidth: function _getAppointmentDefaultWidth() {
return APPOINTMENT_DEFAULT_WIDTH;
},
_needVerticalGroupBounds: function _needVerticalGroupBounds() {
return false;
},
_needHorizontalGroupBounds: function _needHorizontalGroupBounds() {
return false;
}
});
module.exports = BaseRenderingStrategy;