devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
948 lines (947 loc) • 39.4 kB
JavaScript
/**
* DevExtreme (esm/__internal/scheduler/appointments/m_appointment_collection.js)
* Version: 25.2.8
* Build date: Mon Jun 08 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
locate,
move
} from "../../../common/core/animation/translator";
import eventsEngine from "../../../common/core/events/core/events_engine";
import {
name as dblclickEvent
} from "../../../common/core/events/double_click";
import {
addNamespace,
isFakeClickEvent
} from "../../../common/core/events/utils/index";
import registerComponent from "../../../core/component_registrator";
import domAdapter from "../../../core/dom_adapter";
import $ from "../../../core/renderer";
import {
grep,
normalizeKey
} from "../../../core/utils/common";
import dateUtils from "../../../core/utils/date";
import {
isElementInDom
} from "../../../core/utils/dom";
import {
extend
} from "../../../core/utils/extend";
import {
each
} from "../../../core/utils/iterator";
import {
getBoundingRect
} from "../../../core/utils/position";
import {
setOuterHeight,
setOuterWidth
} from "../../../core/utils/size";
import {
isDeferred,
isPlainObject
} from "../../../core/utils/type";
import {
dateUtilsTs
} from "../../core/utils/date";
import CollectionWidget from "../../ui/collection/collection_widget.edit";
import {
APPOINTMENT_SETTINGS_KEY
} from "../constants";
import {
AGENDA_LAST_IN_DATE_APPOINTMENT_CLASS,
APPOINTMENT_CONTENT_CLASSES,
APPOINTMENT_DRAG_SOURCE_CLASS,
APPOINTMENT_ITEM_CLASS
} from "../m_classes";
import timeZoneUtils from "../m_utils_time_zone";
import {
AppointmentAdapter
} from "../utils/appointment_adapter/appointment_adapter";
import {
getTargetedAppointment,
getTargetedAppointmentFromInfo
} from "../utils/get_targeted_appointment";
import {
getAppointmentGroupValues
} from "../utils/resource_manager/appointment_groups_utils";
import {
getGroupTexts
} from "../utils/resource_manager/group_utils";
import {
AgendaAppointment
} from "./appointment/agenda_appointment";
import {
Appointment
} from "./appointment/m_appointment";
import {
createAgendaAppointmentLayout,
createAppointmentLayout
} from "./m_appointment_layout";
import {
AppointmentsKeyboardNavigation
} from "./m_appointments_kbn";
import {
DateFormatType
} from "./m_text_utils";
import {
getAppointmentDateRange
} from "./resizing/m_core";
import {
isNeedToAdd
} from "./utils/get_arrays_diff";
import {
getViewModelDiff
} from "./utils/get_view_model_diff";
const COMPONENT_CLASS = "dx-scheduler-scrollable-appointments";
const DBLCLICK_EVENT_NAME = addNamespace(dblclickEvent, "dxSchedulerAppointment");
const toMs = dateUtils.dateToMilliseconds;
class SchedulerAppointments extends CollectionWidget {
constructor() {
super(...arguments);
this._isResizing = false
}
get isResizing() {
return this._isResizing
}
get isAgendaView() {
return this.invoke("isCurrentViewAgenda")
}
get isVirtualScrolling() {
return this.invoke("isVirtualScrolling")
}
get appointmentDataSource() {
return this.option("getAppointmentDataSource")()
}
get dataAccessors() {
return this.option("dataAccessors")
}
get sortedItems() {
return this.option("getSortedAppointments")()
}
getResourceManager() {
return this.option("getResourceManager")()
}
option(optionName, value) {
return super.option(...arguments)
}
notifyObserver(subject, args) {
const notifyScheduler = this.option("notifyScheduler");
if (notifyScheduler) {
notifyScheduler.invoke(subject, args)
}
}
invoke(funcName) {
const notifyScheduler = this.option("notifyScheduler");
if (notifyScheduler) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key]
}
return notifyScheduler.invoke(funcName, ...args)
}
}
_dispose() {
clearTimeout(this._appointmentClickTimeout);
super._dispose()
}
_supportedKeys() {
const parentValue = super._supportedKeys();
const kbnValue = this._kbn.getSupportedKeys();
return Object.assign({
enter: parentValue.enter,
space: parentValue.space
}, kbnValue)
}
getAppointmentSettings($item) {
return $item.data(APPOINTMENT_SETTINGS_KEY)
}
_moveFocus() {}
_focusTarget() {
return this._kbn.getFocusableItems()
}
_renderFocusTarget() {
var _this$$itemBySortedIn;
if (null !== (_this$$itemBySortedIn = this.$itemBySortedIndex) && void 0 !== _this$$itemBySortedIn && _this$$itemBySortedIn.length) {
this._kbn.resetTabIndex(this._kbn.getFirstVisibleItem())
}
}
_cleanFocusState() {
this._focusedItemIndexBeforeRender = this._kbn.isNavigating ? this._kbn.focusedItemSortIndex : -1;
super._cleanFocusState()
}
_renderFocusState() {
super._renderFocusState();
if (-1 !== this._focusedItemIndexBeforeRender) {
this._kbn.focusedItemSortIndex = this._focusedItemIndexBeforeRender;
this._kbn.isNavigating = false;
this._kbn.focus();
this._focusedItemIndexBeforeRender = -1
} else {
this._kbn.focusedItemSortIndex = -1
}
}
_focusInHandler(e) {
super._focusInHandler(e);
this._kbn.focusInHandler(e)
}
_focusOutHandler(e) {
this._kbn.focusOutHandler();
super._focusOutHandler(e)
}
_eventBindingTarget() {
return this._itemContainer()
}
_getDefaultOptions() {
return extend(super._getDefaultOptions(), {
noDataText: null,
activeStateEnabled: true,
hoverStateEnabled: true,
tabIndex: 0,
fixedContainer: null,
allDayContainer: null,
allowDrag: true,
allowResize: true,
allowAllDayResize: true,
onAppointmentDblClick: null,
groups: [],
resources: []
})
}
getItemsDiff() {
let previousValue = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : [];
let value = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : [];
const elementsInRenderOrder = previousValue.map(_ref => {
let {
sortedIndex: sortedIndex
} = _ref;
return this.$itemBySortedIndex[sortedIndex]
});
const diff = getViewModelDiff(previousValue, value, this.appointmentDataSource);
diff.filter(item => !isNeedToAdd(item)).forEach((item, index) => {
item.element = elementsInRenderOrder[index]
});
return diff
}
_optionChanged(args) {
switch (args.name) {
case "items":
this._cleanFocusState();
if (this.isAgendaView) {
this.forceRepaintAllAppointments(args.value || [])
} else {
const diff = this.getItemsDiff(args.previousValue, args.value);
this.repaintAppointments(diff)
}
this._attachAppointmentsEvents();
break;
case "fixedContainer":
case "allDayContainer":
case "onAppointmentDblClick":
case "allowDelete":
break;
case "allowDrag":
case "allowResize":
case "allowAllDayResize":
this._cleanFocusState();
this.forceRepaintAllAppointments(this.option("items") || []);
this._attachAppointmentsEvents();
break;
case "focusedElement":
this._kbn.resetTabIndex($(args.value));
super._optionChanged(args);
break;
case "focusStateEnabled":
this._clearDropDownItemsElements();
this.renderDropDownAppointments();
super._optionChanged(args);
break;
default:
super._optionChanged(args)
}
}
_applyFragment(fragment, allDay) {
if (fragment.children().length > 0) {
this._getAppointmentContainer(allDay).append(fragment)
}
}
forceRepaintAllAppointments(items) {
this.$itemBySortedIndex = [];
this._renderByFragments(($commonFragment, $allDayFragment) => {
this._getAppointmentContainer(true).html("");
this._getAppointmentContainer(false).html("");
if (0 === items.length) {
this._cleanItemContainer()
}
items.forEach((item, index) => {
const container = item.allDay ? $allDayFragment : $commonFragment;
this._renderItem(index, item, container)
})
})
}
repaintAppointments(diff) {
this.$itemBySortedIndex = [];
const {
appointmentTooltip: appointmentTooltip
} = this.option();
let $newTooltipTarget = null;
let targetViewModel = null;
this._renderByFragments(($commonFragment, $allDayFragment) => {
const isRepaintAll = diff.every(item => Boolean(item.needToAdd ?? item.needToRemove));
if (isRepaintAll) {
this._getAppointmentContainer(true).html("");
this._getAppointmentContainer(false).html("")
}
if (0 === diff.length) {
this._cleanItemContainer()
}
diff.forEach((item, index) => {
if (isRepaintAll && item.needToRemove) {
return
}
if (item.needToRemove) {
var _item$element;
null === (_item$element = item.element) || void 0 === _item$element || _item$element.remove();
return
}
const container = item.item.allDay ? $allDayFragment : $commonFragment;
if (item.needToAdd) {
this._renderItem(index, item.item, container);
return
}
if (item.needToUpdateItems) {
const $oldElement = item.element;
$oldElement.detach();
const $newElement = this._renderItem(index, item.item, container);
if (appointmentTooltip.isShownForTarget($oldElement)) {
$newTooltipTarget = $newElement;
targetViewModel = item.item
}
$oldElement.remove();
return
}
if (item.element) {
item.element.data(APPOINTMENT_SETTINGS_KEY, item.item);
this.$itemBySortedIndex[item.item.sortedIndex] = item.element
}
})
});
this.updateTooltip($newTooltipTarget, targetViewModel)
}
updateTooltip($newTarget, collectorViewModel) {
const {
appointmentTooltip: appointmentTooltip
} = this.option();
if (!appointmentTooltip) {
return
}
if (null !== $newTarget && null !== collectorViewModel) {
const dataList = this.getCompactAppointmentItems(collectorViewModel);
appointmentTooltip.setTarget($newTarget);
appointmentTooltip.setListItems(dataList)
} else {
var _appointmentTooltip$g;
const targetElement = null === (_appointmentTooltip$g = appointmentTooltip.getTarget()) || void 0 === _appointmentTooltip$g ? void 0 : _appointmentTooltip$g[0];
if (targetElement && !targetElement.isConnected) {
appointmentTooltip.hide()
}
}
}
_renderByFragments(renderFunction) {
if (this.isVirtualScrolling) {
const $commonFragment = $(domAdapter.createDocumentFragment());
const $allDayFragment = $(domAdapter.createDocumentFragment());
renderFunction($commonFragment, $allDayFragment);
this._applyFragment($commonFragment, false);
this._applyFragment($allDayFragment, true)
} else {
renderFunction(this._getAppointmentContainer(false), this._getAppointmentContainer(true))
}
}
_refreshActiveDescendant() {}
_attachAppointmentsEvents() {
this._attachClickEvent();
this._attachHoldEvent();
this._attachContextMenuEvent();
this._attachAppointmentDblClick();
this._renderFocusState();
this._attachFeedbackEvents();
this._attachHoverEvents()
}
_clearDropDownItemsElements() {
this.invoke("clearCompactAppointments")
}
_findItemElementByItem(item) {
const result = [];
const that = this;
this.itemElements().each(function() {
const $item = $(this);
if ($item.data(that._itemDataKey()) === item) {
result.push($item)
}
});
return result
}
_itemClass() {
return APPOINTMENT_ITEM_CLASS
}
_itemContainer() {
const $container = super._itemContainer();
let $result = $container;
const $allDayContainer = this.option("allDayContainer");
if ($allDayContainer) {
$result = $container.add($allDayContainer)
}
return $result
}
_cleanItemContainer() {
super._cleanItemContainer();
const $allDayContainer = this.option("allDayContainer");
if ($allDayContainer) {
$allDayContainer.empty()
}
}
_init() {
super._init();
this.$itemBySortedIndex = [];
this._kbn = new AppointmentsKeyboardNavigation(this);
this._focusedItemIndexBeforeRender = -1;
this.$element().addClass(COMPONENT_CLASS);
this._preventSingleAppointmentClick = false
}
_renderAppointmentTemplate($container, appointment, model) {
const config = {
isAllDay: appointment.allDay,
isRecurrence: appointment.recurrenceRule,
html: isPlainObject(appointment) && appointment.html ? appointment.html : void 0
};
let {
targetedAppointmentData: targetedAppointmentData
} = model;
if (this._currentAppointmentSettings && "isAgendaModel" in this._currentAppointmentSettings) {
targetedAppointmentData = getTargetedAppointmentFromInfo(this._currentAppointmentSettings.itemData, this._currentAppointmentSettings, this.dataAccessors, this.getResourceManager(), true)
}
const formatText = this.invoke("createFormattedDateText", appointment, targetedAppointmentData, appointment.allDay ? DateFormatType.DATE : DateFormatType.TIME);
$container.append(this.isAgendaView ? createAgendaAppointmentLayout(formatText, config) : createAppointmentLayout(formatText, config));
if (!this.isAgendaView) {
$container.parent().prepend($("<div>").addClass(APPOINTMENT_CONTENT_CLASSES.STRIP))
}
}
_executeItemRenderAction(index, itemData, itemElement) {
const action = this._getItemRenderAction();
if (action) {
action(this.invoke("mapAppointmentFields", {
itemData: itemData,
itemElement: itemElement
}))
}
delete this._currentAppointmentSettings
}
_itemClickHandler(e) {
super._itemClickHandler(e, {}, {
afterExecute: function(e) {
this._processItemClick(e.args[0].event)
}.bind(this)
})
}
_processItemClick(e) {
const $target = $(e.currentTarget);
const data = this._getItemData($target);
if ($target.is(".dx-scheduler-appointment-collector")) {
return
}
if ("keydown" === e.type || isFakeClickEvent(e)) {
this.notifyObserver("showEditAppointmentPopup", {
data: data,
target: $target
});
return
}
this._appointmentClickTimeout = setTimeout(() => {
if (!this._preventSingleAppointmentClick && isElementInDom($target)) {
this.notifyObserver("showAppointmentTooltip", {
data: data,
target: $target
})
}
this._preventSingleAppointmentClick = false
}, 300)
}
_extendActionArgs($itemElement) {
const args = super._extendActionArgs($itemElement);
return this.invoke("mapAppointmentFields", args)
}
_render() {
super._render();
this._attachAppointmentDblClick()
}
_attachAppointmentDblClick() {
const that = this;
const itemSelector = that._itemSelector();
const itemContainer = this._itemContainer();
eventsEngine.off(itemContainer, DBLCLICK_EVENT_NAME, itemSelector);
eventsEngine.on(itemContainer, DBLCLICK_EVENT_NAME, itemSelector, e => {
that._itemDXEventHandler(e, "onAppointmentDblClick", {}, {
afterExecute(e) {
that._dblClickHandler(e.args[0].event)
}
})
})
}
_dblClickHandler(e) {
const $targetAppointment = $(e.currentTarget);
const appointmentData = this._getItemData($targetAppointment);
clearTimeout(this._appointmentClickTimeout);
this._preventSingleAppointmentClick = true;
this.notifyObserver("showEditAppointmentPopup", {
data: appointmentData,
target: $targetAppointment
})
}
_renderItem(index, item, container) {
if ("items" in item) {
return this.renderDropDownAppointment(container, item)
}
this._currentAppointmentSettings = item;
const $item = super._renderItem(index, item.itemData, container);
$item.data(APPOINTMENT_SETTINGS_KEY, item);
if (-1 !== item.sortedIndex) {
this.$itemBySortedIndex[item.sortedIndex] = $item
}
return $item
}
_getItemContent($itemFrame) {
$itemFrame.data(APPOINTMENT_SETTINGS_KEY, this._currentAppointmentSettings);
const $itemContent = super._getItemContent($itemFrame);
return $itemContent
}
_createItemByTemplate(itemTemplate, renderArgs) {
const {
itemData: itemData,
container: container,
index: index
} = renderArgs;
const parent = $(container).parent();
parent.prepend($("<span>").addClass(APPOINTMENT_CONTENT_CLASSES.ARIA_DESCRIPTION).attr("hidden", true));
return itemTemplate.render({
model: {
appointmentData: itemData,
targetedAppointmentData: this.invoke("getTargetedAppointmentData", itemData, parent)
},
container: container,
index: index
})
}
_getAppointmentContainer(allDay) {
const $allDayContainer = this.option("allDayContainer");
const $container = this.itemsContainer().not($allDayContainer);
return allDay && $allDayContainer ? $allDayContainer : $container
}
_postprocessRenderItem(args) {
this.renderAppointment(args.itemElement, this._currentAppointmentSettings)
}
renderAppointment(element, settings) {
element.data(APPOINTMENT_SETTINGS_KEY, settings);
this._applyResourceDataAttr(element);
if (this.isAgendaView) {
this.renderAgendaAppointment(element, settings);
return
}
this.renderGeneralAppointment(element, settings)
}
renderAgendaAppointment(element, settings) {
if (settings.isLastInGroup) {
element.addClass(AGENDA_LAST_IN_DATE_APPOINTMENT_CLASS)
}
const {
groups: groups,
groupsLeafs: groupsLeafs,
resourceById: resourceById
} = this.getResourceManager();
const config = {
data: settings.itemData,
groupIndex: settings.groupIndex,
groupTexts: getGroupTexts(groups, groupsLeafs, resourceById, settings.groupIndex),
notifyScheduler: this.option("notifyScheduler"),
geometry: settings,
allowResize: false,
allowDrag: false,
groups: this.option("groups"),
dataAccessors: this.option("dataAccessors"),
timeZoneCalculator: this.option("timeZoneCalculator"),
getResourceManager: this.option("getResourceManager")
};
this._createComponent(element, AgendaAppointment, config)
}
renderGeneralAppointment(element, settings) {
var _settings$info;
const allowResize = this.option("allowResize") && !settings.skipResizing;
const allowDrag = this.option("allowDrag");
const {
allDay: allDay
} = settings;
const {
groups: groups,
groupsLeafs: groupsLeafs,
resourceById: resourceById
} = this.getResourceManager();
const isGroupByDate = this.option("groupByDate");
const config = {
data: settings.itemData,
groupIndex: settings.groupIndex,
groupTexts: getGroupTexts(groups, groupsLeafs, resourceById, settings.groupIndex),
notifyScheduler: this.option("notifyScheduler"),
geometry: settings,
direction: settings.direction || "vertical",
allowResize: allowResize,
allowDrag: allowDrag,
allDay: allDay,
reduced: isGroupByDate ? void 0 : settings.reduced,
startDate: new Date(null === (_settings$info = settings.info) || void 0 === _settings$info ? void 0 : _settings$info.appointment.startDate),
cellWidth: this.invoke("getCellWidth"),
cellHeight: this.invoke("getCellHeight"),
resizableConfig: this._resizableConfig(settings.itemData, settings),
groups: this.option("groups"),
partIndex: settings.partIndex,
partTotalCount: settings.partTotalCount,
dataAccessors: this.option("dataAccessors"),
timeZoneCalculator: this.option("timeZoneCalculator"),
getResizableStep: this.option("getResizableStep"),
getResourceManager: this.option("getResourceManager")
};
this._createComponent(element, Appointment, config)
}
_applyResourceDataAttr($appointment) {
const {
resources: resources
} = this.getResourceManager();
const rawAppointment = this._getItemData($appointment);
const appointmentGroups = getAppointmentGroupValues(rawAppointment, resources);
Object.entries(appointmentGroups).forEach(_ref2 => {
let [resourceIndex, resourceIds] = _ref2;
if (resourceIds.length) {
const prefix = `data-${normalizeKey(resourceIndex.toLowerCase())}-`;
resourceIds.forEach(value => $appointment.attr(prefix + normalizeKey(value), true))
}
})
}
_resizableConfig(appointmentData, itemSetting) {
return {
area: this._calculateResizableArea(itemSetting, appointmentData),
onResizeStart: e => {
const $appointment = $(e.element);
this._isResizing = true;
this._kbn.focus($appointment);
if (this.invoke("needRecalculateResizableArea")) {
const updatedArea = this._calculateResizableArea(this.getAppointmentSettings($appointment), $appointment.data("dxItemData"));
e.component.option("area", updatedArea);
e.component._renderDragOffsets(e.event)
}
this._initialSize = {
width: e.width,
height: e.height
};
this._initialCoordinates = locate($appointment)
},
onResizeEnd: e => {
this._isResizing = false;
this._resizeEndHandler(e)
}
}
}
_calculateResizableArea(itemSetting, appointmentData) {
const area = this.$element().closest(".dx-scrollable-content");
return this.invoke("getResizableAppointmentArea", {
coordinates: {
left: itemSetting.left,
top: 0,
groupIndex: itemSetting.groupIndex
},
allDay: itemSetting.allDay
}) || area
}
_resizeEndHandler(e) {
const $element = $(e.element);
const {
allDay: allDay,
info: info
} = $element.data(APPOINTMENT_SETTINGS_KEY);
const sourceAppointment = this._getItemData($element);
const viewOffset = this.invoke("getViewOffsetMs");
let dateRange;
if (allDay) {
dateRange = this.resizeAllDay(e)
} else {
const startDate = this._getEndResizeAppointmentStartDate(e, sourceAppointment, info.appointment);
const {
endDate: endDate
} = info.appointment;
const shiftedStartDate = dateUtilsTs.addOffsets(startDate, -viewOffset);
const shiftedEndDate = dateUtilsTs.addOffsets(endDate, -viewOffset);
dateRange = this.getDateRange(e, shiftedStartDate, shiftedEndDate);
dateRange.startDate = dateUtilsTs.addOffsets(dateRange.startDate, viewOffset);
dateRange.endDate = dateUtilsTs.addOffsets(dateRange.endDate, viewOffset)
}
this.updateResizedAppointment($element, dateRange, this.dataAccessors, this.option("timeZoneCalculator"))
}
resizeAllDay(e) {
const $element = $(e.element);
const timeZoneCalculator = this.option("timeZoneCalculator");
return getAppointmentDateRange({
handles: e.handles,
appointmentSettings: $element.data(APPOINTMENT_SETTINGS_KEY),
isVerticalGroupedWorkSpace: this.option("isVerticalGroupedWorkSpace")(),
appointmentRect: getBoundingRect($element[0]),
parentAppointmentRect: getBoundingRect($element.parent()[0]),
viewDataProvider: this.option("getViewDataProvider")(),
isDateAndTimeView: this.option("isDateAndTimeView")(),
startDayHour: this.invoke("getStartDayHour"),
endDayHour: this.invoke("getEndDayHour"),
timeZoneCalculator: timeZoneCalculator,
dataAccessors: this.dataAccessors,
rtlEnabled: this.option("rtlEnabled"),
DOMMetaData: this.option("getDOMElementsMetaData")(),
viewOffset: this.invoke("getViewOffsetMs")
})
}
updateResizedAppointment($element, dateRange, dataAccessors, timeZoneCalculator) {
const sourceAppointment = this._getItemData($element);
const gridAdapter = new AppointmentAdapter(sourceAppointment, dataAccessors).clone();
gridAdapter.startDate = new Date(dateRange.startDate);
gridAdapter.endDate = new Date(dateRange.endDate);
const convertedBackAdapter = gridAdapter.clone().calculateDates(timeZoneCalculator, "fromGrid").calculateDates(timeZoneCalculator, "toGrid");
const startDateDelta = gridAdapter.startDate.getTime() - convertedBackAdapter.startDate.getTime();
const endDateDelta = gridAdapter.endDate.getTime() - convertedBackAdapter.endDate.getTime();
gridAdapter.startDate = dateUtilsTs.addOffsets(gridAdapter.startDate, startDateDelta);
gridAdapter.endDate = dateUtilsTs.addOffsets(gridAdapter.endDate, endDateDelta);
const data = gridAdapter.calculateDates(timeZoneCalculator, "fromGrid").source;
this.notifyObserver("updateAppointmentAfterResize", {
target: sourceAppointment,
data: data,
$appointment: $element
})
}
_getEndResizeAppointmentStartDate(e, rawAppointment, appointmentInfo) {
const timeZoneCalculator = this.option("timeZoneCalculator");
const appointmentAdapter = new AppointmentAdapter(rawAppointment, this.dataAccessors);
let {
startDate: startDate
} = appointmentInfo;
const {
startDateTimeZone: startDateTimeZone,
isRecurrent: isRecurrent
} = appointmentAdapter;
const isAllDay = this.invoke("isAllDay", rawAppointment);
if (!e.handles.top && !isRecurrent && !isAllDay) {
startDate = timeZoneCalculator.createDate(appointmentAdapter.startDate, "toGrid", startDateTimeZone)
}
return startDate
}
getDateRange(e, startDate, endDate) {
const itemData = this._getItemData(e.element);
const deltaTime = this.invoke("getDeltaTime", e, this._initialSize, itemData);
const renderingStrategyDirection = this.invoke("getRenderingStrategyDirection");
let isStartDateChanged = false;
const isAllDay = this.invoke("isAllDay", itemData);
const needCorrectDates = this.invoke("needCorrectAppointmentDates") && !isAllDay;
let startTime;
let endTime;
if ("vertical" !== renderingStrategyDirection || isAllDay) {
isStartDateChanged = this.option("rtlEnabled") ? e.handles.right : e.handles.left
} else {
isStartDateChanged = e.handles.top
}
if (isStartDateChanged) {
startTime = needCorrectDates ? this._correctStartDateByDelta(startDate, deltaTime) : startDate.getTime() - deltaTime;
startTime += timeZoneUtils.getTimezoneOffsetChangeInMs(startDate, endDate, startTime, endDate);
endTime = endDate.getTime()
} else {
startTime = startDate.getTime();
endTime = needCorrectDates ? this._correctEndDateByDelta(endDate, deltaTime) : endDate.getTime() + deltaTime;
endTime -= timeZoneUtils.getTimezoneOffsetChangeInMs(startDate, endDate, startDate, endTime)
}
return {
startDate: new Date(startTime),
endDate: new Date(endTime)
}
}
_correctEndDateByDelta(endDate, deltaTime) {
const endDayHour = this.invoke("getEndDayHour");
const startDayHour = this.invoke("getStartDayHour");
const maxDate = new Date(endDate);
const minDate = new Date(endDate);
const correctEndDate = new Date(endDate);
minDate.setHours(startDayHour, 0, 0, 0);
maxDate.setHours(endDayHour, 0, 0, 0);
if (correctEndDate > maxDate) {
correctEndDate.setHours(endDayHour, 0, 0, 0)
}
let result = correctEndDate.getTime() + deltaTime;
const visibleDayDuration = (endDayHour - startDayHour) * toMs("hour");
const daysCount = deltaTime > 0 ? Math.ceil(deltaTime / visibleDayDuration) : Math.floor(deltaTime / visibleDayDuration);
if (result > maxDate.getTime() || result <= minDate.getTime()) {
const tailOfCurrentDay = maxDate.getTime() - correctEndDate.getTime();
const tailOfPrevDays = deltaTime - tailOfCurrentDay;
const correctedEndDate = new Date(correctEndDate).setDate(correctEndDate.getDate() + daysCount);
const lastDay = new Date(correctedEndDate);
lastDay.setHours(startDayHour, 0, 0, 0);
result = lastDay.getTime() + tailOfPrevDays - visibleDayDuration * (daysCount - 1)
}
return result
}
_correctStartDateByDelta(startDate, deltaTime) {
const endDayHour = this.invoke("getEndDayHour");
const startDayHour = this.invoke("getStartDayHour");
const maxDate = new Date(startDate);
const minDate = new Date(startDate);
const correctStartDate = new Date(startDate);
minDate.setHours(startDayHour, 0, 0, 0);
maxDate.setHours(endDayHour, 0, 0, 0);
if (correctStartDate < minDate) {
correctStartDate.setHours(startDayHour, 0, 0, 0)
}
let result = correctStartDate.getTime() - deltaTime;
const visibleDayDuration = (endDayHour - startDayHour) * toMs("hour");
const daysCount = deltaTime > 0 ? Math.ceil(deltaTime / visibleDayDuration) : Math.floor(deltaTime / visibleDayDuration);
if (result < minDate.getTime() || result >= maxDate.getTime()) {
const tailOfCurrentDay = correctStartDate.getTime() - minDate.getTime();
const tailOfPrevDays = deltaTime - tailOfCurrentDay;
const firstDay = new Date(correctStartDate.setDate(correctStartDate.getDate() - daysCount));
firstDay.setHours(endDayHour, 0, 0, 0);
result = firstDay.getTime() - tailOfPrevDays + visibleDayDuration * (daysCount - 1)
}
return result
}
renderDropDownAppointments() {
this._renderByFragments(($commonFragment, $allDayFragment) => {
const items = this.option("items") || [];
items.forEach(item => {
if ("items" in item) {
const $fragment = item.allDay ? $allDayFragment : $commonFragment;
this.renderDropDownAppointment($fragment, item)
}
})
})
}
renderDropDownAppointment($fragment, appointment) {
const compactAppointmentItems = this.getCompactAppointmentItems(appointment);
const $item = this.invoke("renderCompactAppointments", {
$container: $fragment,
coordinates: {
top: appointment.top,
left: appointment.left
},
items: compactAppointmentItems,
buttonColor: compactAppointmentItems[0].color,
sortedIndex: appointment.sortedIndex,
width: appointment.width,
height: appointment.height,
onAppointmentClick: this.option("onItemClick"),
allowDrag: this.option("allowDrag"),
isCompact: appointment.isCompact
});
this.$itemBySortedIndex[appointment.sortedIndex] = $item;
return $item
}
getCompactAppointmentItems(appointment) {
const resourceManager = this.getResourceManager();
const result = appointment.items.map(item => {
const appointmentConfig = {
itemData: item.itemData,
groupIndex: appointment.groupIndex
};
return {
appointment: item.itemData,
targetedAppointment: getTargetedAppointment(item.itemData, item, this.dataAccessors, resourceManager),
color: resourceManager.getAppointmentColor(appointmentConfig),
settings: item
}
});
return result
}
moveAppointmentBack(dragEvent) {
const $appointment = this._kbn.$focusTarget();
const size = this._initialSize;
const coords = this._initialCoordinates;
this._isResizing = false;
if (dragEvent) {
this._removeDragSourceClassFromDraggedAppointment();
if (isDeferred(dragEvent.cancel)) {
dragEvent.cancel.resolve(true)
} else {
dragEvent.cancel = true
}
}
if ($appointment.get(0) && !dragEvent) {
if (coords) {
move($appointment, coords);
delete this._initialSize
}
if (size) {
setOuterWidth($appointment, size.width);
setOuterHeight($appointment, size.height);
delete this._initialCoordinates
}
}
}
focus() {
this._kbn.focus()
}
_removeDragSourceClassFromDraggedAppointment() {
const $appointments = this._itemElements().filter(`.${APPOINTMENT_DRAG_SOURCE_CLASS}`);
$appointments.each((_, element) => {
const appointmentInstance = $(element).dxSchedulerAppointment("instance");
appointmentInstance.option("isDragSource", false)
})
}
_setDragSourceAppointment(appointment, settings) {
const $appointments = this._findItemElementByItem(appointment);
const {
startDate: startDate,
endDate: endDate
} = settings.info.sourceAppointment;
const {
groupIndex: groupIndex
} = settings;
$appointments.forEach($item => {
const {
info: itemInfo,
groupIndex: itemGroupIndex
} = $item.data(APPOINTMENT_SETTINGS_KEY);
const {
startDate: itemStartDate,
endDate: itemEndDate
} = itemInfo.sourceAppointment;
const appointmentInstance = $item.dxSchedulerAppointment("instance");
const isDragSource = startDate.getTime() === itemStartDate.getTime() && endDate.getTime() === itemEndDate.getTime() && groupIndex === itemGroupIndex;
appointmentInstance.option("isDragSource", isDragSource)
})
}
updateResizableArea() {
const $allResizableElements = this.$element().find(".dx-scheduler-appointment.dx-resizable");
const horizontalResizables = grep($allResizableElements, el => {
const $el = $(el);
const resizableInst = $el.dxResizable("instance");
const {
area: area,
handles: handles
} = resizableInst.option();
return ("right left" === handles || "left right" === handles) && isPlainObject(area)
});
each(horizontalResizables, (_, el) => {
const $el = $(el);
const position = locate($el);
const appointmentData = this._getItemData($el);
const area = this._calculateResizableArea({
left: position.left
}, appointmentData);
$el.dxResizable("instance").option("area", area)
})
}
}
registerComponent("dxSchedulerAppointments", SchedulerAppointments);
export default SchedulerAppointments;