Skip to content

Commit e2e05b9

Browse files
authored
Scheduler: avoid double date transform out of view model to display date (#30971)
Co-authored-by: Vladimir Bushmanov <[email protected]>
1 parent 01c10b1 commit e2e05b9

30 files changed

+534
-284
lines changed

packages/devextreme/js/__internal/scheduler/__mock__/appointment_data_accessor.mock.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,18 @@ export const mockFieldExpressions: IFieldExpr = {
1515
visibleExpr: 'visible',
1616
};
1717

18+
export const mockUppercaseFieldExpressions: IFieldExpr = {
19+
startDateExpr: 'StartDate',
20+
endDateExpr: 'EndDate',
21+
startDateTimeZoneExpr: 'StartDateTimeZone',
22+
endDateTimeZoneExpr: 'EndDateTimeZone',
23+
allDayExpr: 'AllDay',
24+
textExpr: 'Text',
25+
descriptionExpr: 'Description',
26+
recurrenceRuleExpr: 'RecurrenceRule',
27+
recurrenceExceptionExpr: 'RecurrenceException',
28+
disabledExpr: 'Disabled',
29+
visibleExpr: 'Visible',
30+
};
31+
1832
export const mockAppointmentDataAccessor = new AppointmentDataAccessor(mockFieldExpressions, true);

packages/devextreme/js/__internal/scheduler/appointments/m_appointment_collection.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ import { APPOINTMENT_SETTINGS_KEY } from '../constants';
2828
import { APPOINTMENT_CONTENT_CLASSES, APPOINTMENT_DRAG_SOURCE_CLASS, APPOINTMENT_ITEM_CLASS } from '../m_classes';
2929
import { getRecurrenceProcessor } from '../m_recurrence';
3030
import timeZoneUtils from '../m_utils_time_zone';
31+
import type { CompactAppointmentOptions } from '../types';
3132
import { AppointmentAdapter } from '../utils/appointment_adapter/appointment_adapter';
3233
import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor';
34+
import {
35+
getTargetedAppointment,
36+
getTargetedAppointmentFromInfo,
37+
} from '../utils/get_targeted_appointment';
3338
import { getAppointmentGroupValues } from '../utils/resource_manager/appointment_groups_utils';
3439
import { getGroupTexts } from '../utils/resource_manager/group_utils';
40+
import type { ResourceManager } from '../utils/resource_manager/resource_manager';
3541
import type {
3642
AppointmentAgendaViewModel,
3743
AppointmentCollectorViewModel,
@@ -98,6 +104,10 @@ class SchedulerAppointments extends CollectionWidget {
98104
return countVisibleAppointments(this.option('items') ?? []);
99105
}
100106

107+
getResourceManager(): ResourceManager {
108+
return this.option('getResourceManager')();
109+
}
110+
101111
// eslint-disable-next-line @typescript-eslint/no-unused-vars
102112
option(optionName?: string, value?: any) {
103113
return super.option(...arguments);
@@ -463,10 +473,20 @@ class SchedulerAppointments extends CollectionWidget {
463473
? appointment.html : undefined,
464474
};
465475

476+
let { targetedAppointmentData } = model;
477+
if (this._currentAppointmentSettings && 'isAgendaModel' in this._currentAppointmentSettings) {
478+
targetedAppointmentData = getTargetedAppointmentFromInfo(
479+
this._currentAppointmentSettings.itemData,
480+
this._currentAppointmentSettings,
481+
this.dataAccessors,
482+
this.getResourceManager(),
483+
);
484+
}
485+
466486
const formatText = this.invoke(
467-
'getTextAndFormatDate',
468-
model.appointmentData,
469-
(this._currentAppointmentSettings as any)?.agendaSettings || model.targetedAppointmentData,
487+
'createFormattedDateText',
488+
appointment,
489+
targetedAppointmentData,
470490
'TIME',
471491
);
472492

@@ -637,7 +657,7 @@ class SchedulerAppointments extends CollectionWidget {
637657
element: dxElementWrapper,
638658
settings: AppointmentAgendaViewModel,
639659
): void {
640-
const { groups, groupsLeafs, resourceById } = this.option('getResourceManager')();
660+
const { groups, groupsLeafs, resourceById } = this.getResourceManager();
641661
const config: any = {
642662
data: settings.itemData,
643663
groupIndex: settings.groupIndex,
@@ -663,7 +683,7 @@ class SchedulerAppointments extends CollectionWidget {
663683
const allowResize = this.option('allowResize') && (!isDefined(settings.skipResizing) || isString(settings.skipResizing));
664684
const allowDrag = this.option('allowDrag');
665685
const { allDay } = settings;
666-
const { groups, groupsLeafs, resourceById } = this.option('getResourceManager')();
686+
const { groups, groupsLeafs, resourceById } = this.getResourceManager();
667687
const config: any = {
668688
data: settings.itemData,
669689
groupIndex: settings.groupIndex,
@@ -694,7 +714,7 @@ class SchedulerAppointments extends CollectionWidget {
694714
}
695715

696716
_applyResourceDataAttr($appointment) {
697-
const { resources } = this.option('getResourceManager')();
717+
const { resources } = this.getResourceManager();
698718
const rawAppointment = (this as any)._getItemData($appointment);
699719
const appointmentGroups = getAppointmentGroupValues(rawAppointment, resources);
700720

@@ -982,18 +1002,27 @@ class SchedulerAppointments extends CollectionWidget {
9821002
appointment: AppointmentCollectorViewModel,
9831003
): dxElementWrapper {
9841004
const virtualItems = appointment.items;
985-
const items: any = { data: [], colors: [], settings: [] };
1005+
const items: CompactAppointmentOptions['items'] = [];
9861006
virtualItems.forEach((item) => {
9871007
const appointmentConfig = {
9881008
itemData: item.itemData,
9891009
groupIndex: appointment.groupIndex,
9901010
groups: this.option('groups'),
9911011
};
992-
const buttonColor = this.option('getAppointmentColor')(appointmentConfig);
993-
994-
items.data.push(item.itemData);
995-
items.colors.push(buttonColor);
996-
items.settings.push(item);
1012+
const resourceManager = this.getResourceManager();
1013+
1014+
items.push({
1015+
appointment: item.itemData,
1016+
targetedAppointment: getTargetedAppointment(
1017+
item.itemData,
1018+
item,
1019+
this.dataAccessors,
1020+
this.option('timeZoneCalculator'),
1021+
resourceManager,
1022+
),
1023+
color: resourceManager.getAppointmentColor(appointmentConfig),
1024+
settings: item,
1025+
});
9971026
});
9981027

9991028
const $item = this.invoke('renderCompactAppointments', {
@@ -1003,7 +1032,7 @@ class SchedulerAppointments extends CollectionWidget {
10031032
left: appointment.left,
10041033
},
10051034
items,
1006-
buttonColor: items.colors[0],
1035+
buttonColor: items[0].color,
10071036
sortedIndex: appointment.sortedIndex,
10081037
width: appointment.width,
10091038
height: appointment.height,

packages/devextreme/js/__internal/scheduler/appointments/utils/count_visible_appointments.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ const createAppointment = (date: number) => ({
1010
describe('countVisibleAppointments', () => {
1111
it('should return correct number of agenda appointments', () => {
1212
expect(countVisibleAppointments([
13-
{ itemData: 1, agendaSettings: createAppointment(0) } as any,
14-
{ itemData: 2, agendaSettings: createAppointment(1) } as any,
15-
{ itemData: 3, agendaSettings: createAppointment(2) } as any,
13+
{ itemData: 1, isAgendaModel: true } as any,
14+
{ itemData: 2, isAgendaModel: true } as any,
15+
{ itemData: 3, isAgendaModel: true } as any,
1616
])).toBe(3);
1717
});
1818

packages/devextreme/js/__internal/scheduler/appointments/utils/count_visible_appointments.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export const countVisibleAppointments = (items: AppointmentViewModelPlain[]): nu
2727
return count + item.items.filter(countPart).length;
2828
}
2929

30+
if ('isAgendaModel' in item) {
31+
return count + 1;
32+
}
33+
3034
if ('info' in item && !countPart(item)) {
3135
return count;
3236
}

packages/devextreme/js/__internal/scheduler/appointments/utils/get_view_model_diff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { getArraysDiff } from './get_arrays_diff';
99
const getObjectToCompare = (
1010
item: AppointmentViewModelPlain,
1111
): object => {
12-
if ('agendaSettings' in item) {
12+
if ('isAgendaModel' in item) {
1313
return {};
1414
}
1515

packages/devextreme/js/__internal/scheduler/m_appointment_drag_behavior.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Draggable from '@js/ui/draggable';
55

66
import { APPOINTMENT_SETTINGS_KEY, LIST_ITEM_DATA_KEY } from './constants';
77
import { isSchedulerComponent } from './utils/is_scheduler_component';
8+
import type { AppointmentViewModelPlain } from './view_model/generate_view_model/types';
89

910
const APPOINTMENT_ITEM_CLASS = 'dx-scheduler-appointment';
1011

@@ -85,9 +86,9 @@ export default class AppointmentDragBehavior {
8586
return itemDataFromTooltip || itemDataFromGrid;
8687
}
8788

88-
getItemSettings(appointment) {
89+
getItemSettings(appointment): AppointmentViewModelPlain | undefined {
8990
const itemData: any = $(appointment).data(LIST_ITEM_DATA_KEY);
90-
return itemData?.settings || [];
91+
return itemData?.settings;
9192
}
9293

9394
createDragStartHandler(options, appointmentDragging) {

packages/devextreme/js/__internal/scheduler/m_compact_appointments_helper.ts

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { FunctionTemplate } from '@js/core/templates/function_template';
66
import Button from '@js/ui/button';
77

88
import { APPOINTMENT_SETTINGS_KEY, LIST_ITEM_CLASS, LIST_ITEM_DATA_KEY } from './constants';
9-
import type { AppointmentTooltipItem } from './types';
9+
import type { AppointmentTooltipItem, CompactAppointmentOptions } from './types';
1010

1111
const APPOINTMENT_COLLECTOR_CLASS = 'dx-scheduler-appointment-collector';
1212
const COMPACT_APPOINTMENT_COLLECTOR_CLASS = `${APPOINTMENT_COLLECTOR_CLASS}-compact`;
@@ -18,15 +18,15 @@ export class CompactAppointmentsHelper {
1818
constructor(public instance) {
1919
}
2020

21-
render(options): dxElementWrapper {
21+
render(options: CompactAppointmentOptions): dxElementWrapper {
2222
const { isCompact, items } = options;
2323

24-
const template = this._createTemplate(items.data.length, isCompact);
24+
const template = this._createTemplate(items.length, isCompact);
2525
const button = this._createCompactButton(template, options);
2626
const $button = button.$element();
2727

2828
this.elements.push($button);
29-
$button.data('items', this._createTooltipInfos(items));
29+
$button.data('items', items);
3030

3131
return $button;
3232
}
@@ -39,28 +39,7 @@ export class CompactAppointmentsHelper {
3939
this.elements = [];
4040
}
4141

42-
_createTooltipInfos(items) {
43-
return items.data.map((appointment, index) => {
44-
const targeted = { ...appointment };
45-
46-
if (items.settings?.length > 0) {
47-
const { info } = items.settings[index];
48-
this.instance._dataAccessors.set('startDate', targeted, info.sourceAppointment.startDate);
49-
this.instance._dataAccessors.set('endDate', targeted, info.sourceAppointment.endDate);
50-
}
51-
52-
const tooltipInfo: AppointmentTooltipItem = {
53-
appointment,
54-
targetedAppointment: targeted,
55-
color: items.colors?.[index] ?? [],
56-
settings: items.settings?.[index] ?? [],
57-
};
58-
59-
return tooltipInfo;
60-
});
61-
}
62-
63-
_onButtonClick(e, options) {
42+
_onButtonClick(e, options: CompactAppointmentOptions) {
6443
const $button = $(e.element);
6544
this.instance.showAppointmentTooltipCore(
6645
$button,
@@ -69,7 +48,7 @@ export class CompactAppointmentsHelper {
6948
);
7049
}
7150

72-
_getExtraOptionsForTooltip(options, $appointmentCollector) {
51+
_getExtraOptionsForTooltip(options: CompactAppointmentOptions, $appointmentCollector) {
7352
return {
7453
clickEvent: this._clickEvent(options.onAppointmentClick).bind(this),
7554
dragBehavior: options.allowDrag && this._createTooltipDragBehavior($appointmentCollector).bind(this),
@@ -115,7 +94,7 @@ export class CompactAppointmentsHelper {
11594
});
11695
}
11796

118-
_createCompactButton(template, options) {
97+
_createCompactButton(template, options: CompactAppointmentOptions) {
11998
const $button = this._createCompactButtonElement(options);
12099

121100
return this.instance._createComponent($button, Button, {
@@ -129,8 +108,8 @@ export class CompactAppointmentsHelper {
129108

130109
_createCompactButtonElement({
131110
isCompact, $container, coordinates, sortedIndex, items,
132-
}) {
133-
const appointmentDate = this._getDateText(items.data[0]);
111+
}: CompactAppointmentOptions) {
112+
const appointmentDate = this._getDateText(items[0].appointment);
134113
const result = $('<div>')
135114
.addClass(APPOINTMENT_COLLECTOR_CLASS)
136115
.attr('aria-roledescription', appointmentDate)
@@ -144,11 +123,11 @@ export class CompactAppointmentsHelper {
144123
return result;
145124
}
146125

147-
_renderTemplate(template, items, isCompact) {
126+
_renderTemplate(template, items: AppointmentTooltipItem[], isCompact) {
148127
return new (FunctionTemplate as any)((options) => template.render({
149128
model: {
150-
appointmentCount: items.data.length,
151-
items: items.data,
129+
appointmentCount: items.length,
130+
items: items.map((item) => item.appointment),
152131
isCompact,
153132
},
154133
container: options.container,

0 commit comments

Comments
 (0)