diff --git a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/create_appointment_popup.ts b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/create_appointment_popup.ts index 6e5fa1f384fb..8dfc1e3e3ee8 100644 --- a/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/create_appointment_popup.ts +++ b/packages/devextreme/js/__internal/scheduler/__tests__/__mock__/create_appointment_popup.ts @@ -114,23 +114,21 @@ export const createAppointmentPopup = async ( const onSave = options.onSave ?? jest.fn<(appointment: Record) => PromiseLike>(resolvedDeferred); - const formSchedulerProxy = { - getResourceById: (): Record => resourceManager.resourceById, - getDataAccessors: (): AppointmentDataAccessor => dataAccessors, + const formConfig = { + dataAccessors, + editing, + resourceManager, + firstDayOfWeek: options.firstDayOfWeek ?? 0, + startDayHour: options.startDayHour ?? 0, createComponent, - getEditingConfig: (): typeof editing => editing, - getResourceManager: (): ResourceManager => resourceManager, - getFirstDayOfWeek: (): number => options.firstDayOfWeek ?? 0, - getStartDayHour: (): number => options.startDayHour ?? 0, getCalculatedEndDate: (startDate: Date): Date => { const endDate = new Date(startDate); endDate.setHours(endDate.getHours() + 1); return endDate; }, - getTimeZoneCalculator: (): typeof timeZoneCalculator => timeZoneCalculator, }; - const form = new AppointmentForm(formSchedulerProxy); + const form = new AppointmentForm(formConfig); const noop = (): void => {}; diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts index f399691e525d..25ae8163d8f9 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts @@ -28,6 +28,7 @@ import type Popup from '@ts/ui/popup/m_popup'; import timeZoneUtils from '../m_utils_time_zone'; import type { SafeAppointment } from '../types'; +import type { AppointmentDataAccessor } from '../utils/data_accessor/appointment_data_accessor'; import type { ResourceLoader } from '../utils/loader/resource_loader'; import { DEFAULT_ICONS_SHOW_MODE } from '../utils/options/constants'; import { getAppointmentGroupIndex, getRawAppointmentGroupValues, getSafeGroupValues } from '../utils/resource_manager/appointment_groups_utils'; @@ -37,6 +38,24 @@ import { customizeFormItems } from './m_customize_form_items'; import { RecurrenceForm } from './m_recurrence_form'; import { createFormIconTemplate, getStartDateCommonConfig, RecurrenceRule } from './utils'; +type SchedulerEditingObject = Exclude, boolean>; + +export type CreateComponentFn = ( + element: string | HTMLElement | dxElementWrapper | Element, + Component: any, + options: any, +) => any; + +export interface AppointmentFormConfig { + dataAccessors: AppointmentDataAccessor; + editing: SchedulerProperties['editing']; + resourceManager: ResourceManager; + firstDayOfWeek: number; + startDayHour: number; + createComponent: CreateComponentFn; + getCalculatedEndDate: (startDate: Date) => Date; +} + const CLASSES = { form: 'dx-scheduler-form', icon: 'dx-icon', @@ -118,9 +137,7 @@ const RESOURCES_GROUP_ICON_NAME = 'resourcesGroupIcon'; const DESCRIPTION_ICON_NAME = 'descriptionIcon'; export class AppointmentForm { - private readonly scheduler: any; - - private readonly resourceManager!: ResourceManager; + private readonly config: AppointmentFormConfig; private dxFormInstance?: dxForm; @@ -132,6 +149,10 @@ export class AppointmentForm { private $recurrenceGroup?: dxElementWrapper; + private get resourceManager(): ResourceManager { + return this.config.resourceManager; + } + get dxForm(): dxForm { return this.dxFormInstance as dxForm; } @@ -158,29 +179,28 @@ export class AppointmentForm { } get startDate(): Date | null { - const { startDateExpr } = this.scheduler.getDataAccessors().expr; + const { startDateExpr } = this.config.dataAccessors.expr; const value = this.getFormDataField(startDateExpr); return value ? new Date(dateSerialization.deserializeDate(value)) : null; } get endDate(): Date | null { - const { endDateExpr } = this.scheduler.getDataAccessors().expr; + const { endDateExpr } = this.config.dataAccessors.expr; const value = this.getFormDataField(endDateExpr); return value ? new Date(dateSerialization.deserializeDate(value)) : null; } get recurrenceRuleRaw(): string | null { - const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr; + const { recurrenceRuleExpr } = this.config.dataAccessors.expr; const value = this.getFormDataField(recurrenceRuleExpr) as string | undefined; return value ?? null; } - constructor(scheduler: any) { - this.scheduler = scheduler; - this.resourceManager = scheduler.getResourceManager(); + constructor(config: AppointmentFormConfig) { + this.config = config; } private getFormDataField(field: string): any { @@ -200,7 +220,10 @@ export class AppointmentForm { const mainGroup = this.createMainFormGroup(); - this.recurrenceForm = new RecurrenceForm(this.scheduler); + this.recurrenceForm = new RecurrenceForm({ + firstDayOfWeek: this.config.firstDayOfWeek, + createComponent: this.config.createComponent, + }); const recurrenceGroup = this.recurrenceForm.createRecurrenceFormGroup(); const items = [mainGroup, recurrenceGroup]; @@ -212,28 +235,33 @@ export class AppointmentForm { this.applyFormItemDefaults(mainGroup, showMainGroupIcons); this.applyFormItemDefaults(recurrenceGroup, showRecurrenceGroupIcons); - const editingConfig = this.scheduler.getEditingConfig(); - const customizedItems = customizeFormItems(items, editingConfig?.form?.items); + const customizedItems = customizeFormItems(items, this.getEditingForm()?.items); this.createForm(customizedItems); } - private getIconsShowMode(): AppointmentFormIconsShowMode { - const editingConfig = this.scheduler.getEditingConfig() as SchedulerProperties['editing']; + private getEditingForm(): SchedulerEditingObject['form'] { + const editing = this.getEditingObject(); + return editing?.form; + } - if (isBoolean(editingConfig)) { - return DEFAULT_ICONS_SHOW_MODE; + private getEditingObject(): SchedulerEditingObject | undefined { + const { editing } = this.config; + if (isBoolean(editing) || !editing) { + return undefined; } + return editing; + } - return editingConfig?.form?.iconsShowMode ?? DEFAULT_ICONS_SHOW_MODE; + private getIconsShowMode(): AppointmentFormIconsShowMode { + return this.getEditingForm()?.iconsShowMode ?? DEFAULT_ICONS_SHOW_MODE; } private createForm(items: FormProperties['items']): dxForm { const element = $('
'); - const editingConfig = this.scheduler.getEditingConfig(); const { items: formItems, onContentReady, onInitialized, ...customFormOptions - } = editingConfig?.form ?? {}; + } = this.getEditingForm() ?? {}; const defaultOptions: FormProperties = { items, @@ -251,7 +279,7 @@ export class AppointmentForm { onFieldDataChanged: (e) => { const { startDateExpr, endDateExpr, recurrenceRuleExpr, - } = this.scheduler.getDataAccessors().expr; + } = this.config.dataAccessors.expr; const { dataField } = e; @@ -261,7 +289,7 @@ export class AppointmentForm { const isDateRangeChanged = [startDateExpr, endDateExpr].includes(dataField); const isRecurrenceRuleChanged = dataField === recurrenceRuleExpr; - const isResourceChanged = Object.keys(this.scheduler.getResourceById()).includes(dataField); + const isResourceChanged = Object.keys(this.config.resourceManager.resourceById).includes(dataField); if (isDateRangeChanged) { this.updateDateEditorsValues(); @@ -300,7 +328,7 @@ export class AppointmentForm { } as FormProperties; const formOptions = extend(true, defaultOptions, customFormOptions); - return this.scheduler.createComponent(element, dxForm, formOptions) as dxForm; + return this.config.createComponent(element, dxForm, formOptions) as dxForm; } private createMainFormGroup(): GroupItem { @@ -320,7 +348,7 @@ export class AppointmentForm { } private createSubjectGroup(): GroupItem { - const { textExpr } = this.scheduler.getDataAccessors().expr; + const { textExpr } = this.config.dataAccessors.expr; return { name: SUBJECT_GROUP_NAME, @@ -375,7 +403,7 @@ export class AppointmentForm { } private createAllDaySwitch(): SimpleItem { - const { allDayExpr, startDateExpr, endDateExpr } = this.scheduler.getDataAccessors().expr; + const { allDayExpr, startDateExpr, endDateExpr } = this.config.dataAccessors.expr; return { name: ALL_DAY_EDITOR_NAME, @@ -403,10 +431,9 @@ export class AppointmentForm { this.dxForm.updateData(startDateExpr, allDayStartDate); this.dxForm.updateData(endDateExpr, allDayStartDate); } else { - const startHour = this.scheduler.getStartDayHour(); - startDate.setHours(startHour); + startDate.setHours(this.config.startDayHour); - const calculatedEndDate = this.scheduler.getCalculatedEndDate(startDate); + const calculatedEndDate = this.config.getCalculatedEndDate(startDate); this.dxForm.updateData(startDateExpr, startDate); this.dxForm.updateData(endDateExpr, calculatedEndDate); @@ -419,7 +446,7 @@ export class AppointmentForm { private createStartDateGroup(): GroupItem { const { startDateExpr, startDateTimeZoneExpr, endDateTimeZoneExpr, - } = this.scheduler.getDataAccessors().expr; + } = this.config.dataAccessors.expr; return this.createDateGroup( startDateExpr, @@ -459,7 +486,7 @@ export class AppointmentForm { } private createEndDateGroup(): GroupItem { - const { endDateExpr, endDateTimeZoneExpr } = this.scheduler.getDataAccessors().expr; + const { endDateExpr, endDateTimeZoneExpr } = this.config.dataAccessors.expr; return this.createDateGroup( endDateExpr, @@ -498,8 +525,8 @@ export class AppointmentForm { timeItemOptions?: SimpleItem, timezoneItemOptions?: SimpleItem, ): GroupItem { - const { allowTimeZoneEditing } = this.scheduler.getEditingConfig(); - const { startDateExpr, endDateExpr } = this.scheduler.getDataAccessors().expr; + const allowTimeZoneEditing = this.getEditingObject()?.allowTimeZoneEditing; + const { startDateExpr, endDateExpr } = this.config.dataAccessors.expr; const isStartDateEditor = dateExpr === startDateExpr; const getEditorsDate = (): Date | null => (isStartDateEditor ? this.startDate : this.endDate); @@ -567,7 +594,7 @@ export class AppointmentForm { items: [ extend( true, - getStartDateCommonConfig(this.scheduler.getFirstDayOfWeek()), + getStartDateCommonConfig(this.config.firstDayOfWeek), { editorOptions: { onValueChanged: (e) => { @@ -593,7 +620,7 @@ export class AppointmentForm { type: 'time', useMaskBehavior: true, calendarOptions: { - firstDayOfWeek: this.scheduler.getFirstDayOfWeek(), + firstDayOfWeek: this.config.firstDayOfWeek, }, onValueChanged: (e) => { dateValueChanged(e, (date: Date): void => { @@ -625,7 +652,7 @@ export class AppointmentForm { } private createRepeatGroup(): GroupItem { - const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr; + const { recurrenceRuleExpr } = this.config.dataAccessors.expr; return { name: REPEAT_GROUP_NAME, @@ -677,7 +704,7 @@ export class AppointmentForm { } private createDescriptionGroup(): GroupItem { - const { descriptionExpr } = this.scheduler.getDataAccessors().expr; + const { descriptionExpr } = this.config.dataAccessors.expr; return { name: DESCRIPTION_GROUP_NAME, @@ -709,7 +736,7 @@ export class AppointmentForm { } private createResourcesGroup(): GroupItem { - const resourcesLoaders: ResourceLoader[] = Object.values(this.scheduler.getResourceById()); + const resourcesLoaders: ResourceLoader[] = Object.values(this.config.resourceManager.resourceById); let resourcesItems: FormItem[] = resourcesLoaders.map((resourceLoader) => { const { dataSource, dataAccessor } = resourceLoader; @@ -854,8 +881,7 @@ export class AppointmentForm { showMainGroup(): void { const currentHeight = this.dxPopup.option('height') as string | number | undefined; - const editingConfig = this.scheduler.getEditingConfig(); - const configuredHeight = editingConfig?.popup?.height ?? 'auto'; + const configuredHeight = this.getEditingObject()?.popup?.height ?? 'auto'; if (typeof currentHeight === 'number') { this.dxPopup.option('height', configuredHeight); @@ -908,7 +934,7 @@ export class AppointmentForm { saveRecurrenceValue(): void { const { recurrenceRule } = this.recurrenceForm; - const { recurrenceRuleExpr } = this.scheduler.getDataAccessors().expr; + const { recurrenceRuleExpr } = this.config.dataAccessors.expr; const recurrenceRuleSerialized = recurrenceRule.toString() ?? ''; @@ -1003,7 +1029,7 @@ export class AppointmentForm { } private updateDateTimeEditorsVisibility(): void { - const { allDayExpr } = this.scheduler.getDataAccessors().expr; + const { allDayExpr } = this.config.dataAccessors.expr; const visible = !this.getFormDataField(allDayExpr); const dateOptionsGroupPath = `${MAIN_GROUP_NAME}.${DATE_GROUP_NAME}.${DATE_OPTIONS_GROUP_NAME}`; diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/m_recurrence_form.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/m_recurrence_form.ts index 0247a77a473d..6a00f615b494 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/m_recurrence_form.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/m_recurrence_form.ts @@ -10,7 +10,6 @@ import type { Properties as NumberBoxProperties } from '@js/ui/number_box'; import type { Properties as RadioGroupProperties } from '@js/ui/radio_group'; import type { Properties as SelectBoxProperties } from '@js/ui/select_box'; -import type Scheduler from '../m_scheduler'; import { getRecurrenceFrequencyItems, getRecurrenceMonthItems, @@ -19,6 +18,15 @@ import { } from './localized_items'; import { createFormIconTemplate, getStartDateCommonConfig, RecurrenceRule } from './utils'; +export interface RecurrenceFormConfig { + firstDayOfWeek: number; + createComponent: ( + element: string | HTMLElement | dxElementWrapper | Element, + Component: any, + options: any, + ) => any; +} + const CLASSES = { groupWithIcon: 'dx-scheduler-form-group-with-icon', formIcon: 'dx-scheduler-form-icon', @@ -84,7 +92,7 @@ const RECURRENCE_GROUP_NAME = 'recurrenceGroup'; export class RecurrenceForm { recurrenceRule: RecurrenceRule = new RecurrenceRule('', new Date()); - private readonly scheduler: any; + private readonly config: RecurrenceFormConfig; private dxFormInstance?: dxForm; @@ -92,8 +100,8 @@ export class RecurrenceForm { private readOnly = false; - constructor(scheduler: Scheduler) { - this.scheduler = scheduler; + constructor(config: RecurrenceFormConfig) { + this.config = config; } private createByMonthDayNumberBoxItem( @@ -166,7 +174,7 @@ export class RecurrenceForm { }, extend( true, - getStartDateCommonConfig(this.scheduler.getFirstDayOfWeek()), + getStartDateCommonConfig(this.config.firstDayOfWeek), { name: EDITOR_NAMES.recurrenceStartDateEditor, cssClass: CLASSES.recurrenceStartDateEditor, @@ -307,13 +315,13 @@ export class RecurrenceForm { }, template: (): dxElementWrapper => { const $container = $('
').addClass(CLASSES.daysOfWeekButtons); - const weekDayItems = getRecurrenceWeekDayItems(this.scheduler.getFirstDayOfWeek()); + const weekDayItems = getRecurrenceWeekDayItems(this.config.firstDayOfWeek); weekDayItems.forEach((item) => { const buttonContainer = $('
').appendTo($container); this.weekDayButtons[item.key]?.dispose(); - this.weekDayButtons[item.key] = this.scheduler.createComponent(buttonContainer, Button, { + this.weekDayButtons[item.key] = this.config.createComponent(buttonContainer, Button, { text: item.text, disabled: this.readOnly, onContentReady: (e): void => { @@ -472,7 +480,7 @@ export class RecurrenceForm { type: 'date', useMaskBehavior: true, calendarOptions: { - firstDayOfWeek: this.scheduler.getFirstDayOfWeek(), + firstDayOfWeek: this.config.firstDayOfWeek, }, inputAttr: { 'aria-label': messageLocalization.format('dxScheduler-recurrenceUntilDateLabel'), @@ -546,7 +554,7 @@ export class RecurrenceForm { if (recurrenceRule.byDay.length === 0) { const defaultByDay = [ - ICAL_WEEK_DAYS[startDate?.getDay() ?? this.scheduler.getFirstDayOfWeek()], + ICAL_WEEK_DAYS[startDate?.getDay() ?? this.config.firstDayOfWeek], ]; recurrenceRule.byDay = defaultByDay; diff --git a/packages/devextreme/js/__internal/scheduler/appointment_popup/utils.ts b/packages/devextreme/js/__internal/scheduler/appointment_popup/utils.ts index 7acc0615dd6a..6bb29ddd4ff9 100644 --- a/packages/devextreme/js/__internal/scheduler/appointment_popup/utils.ts +++ b/packages/devextreme/js/__internal/scheduler/appointment_popup/utils.ts @@ -13,7 +13,7 @@ import type { Rule } from '../recurrence/types'; export const createFormIconTemplate = (iconName: string): () => dxElementWrapper => (): dxElementWrapper => getImageContainer(iconName) ?? $('
').addClass('dx-scheduler-form-icon-sized-gap'); -export const getStartDateCommonConfig = (firstDayOfWeek: string): SimpleItem => ({ +export const getStartDateCommonConfig = (firstDayOfWeek: number): SimpleItem => ({ colSpan: 1, itemType: 'simple', editorType: 'dxDateBox', diff --git a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts index 66fa3dcb5f61..71491e4a3451 100644 --- a/packages/devextreme/js/__internal/scheduler/m_scheduler.ts +++ b/packages/devextreme/js/__internal/scheduler/m_scheduler.ts @@ -37,7 +37,7 @@ import { dateUtilsTs } from '@ts/core/utils/date'; import { createA11yStatusContainer } from './a11y_status/a11y_status_render'; import { getA11yStatusText } from './a11y_status/a11y_status_text'; -import { AppointmentForm } from './appointment_popup/m_form'; +import { AppointmentForm, type AppointmentFormConfig } from './appointment_popup/m_form'; import { AppointmentPopup } from './appointment_popup/m_popup'; import AppointmentCollection from './appointments/m_appointment_collection'; import type { AppointmentsProperties } from './appointments_new/appointments'; @@ -276,7 +276,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { case 'firstDayOfWeek': this.updateOption('workSpace', name, value); this.updateOption('header', name, value); - this.cleanPopup(); + this.createAppointmentPopupForm(); break; case 'currentDate': { const dateValue = this.getViewOption(name); @@ -397,6 +397,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.setRemoteFilterIfNeeded(); this.postponeDataSourceLoading(); + this.createAppointmentPopupForm(); break; // TODO Vinogradov refactoring: merge it with startDayHour / endDayHour case 'offset': @@ -506,7 +507,7 @@ class Scheduler extends SchedulerOptionsBaseWidget { this.bringEditingModeToAppointments(editing); this.hideAppointmentTooltip(); - this.cleanPopup(); + this.createAppointmentPopupForm(); break; } case 'showAllDayPanel': @@ -1134,22 +1135,18 @@ class Scheduler extends SchedulerOptionsBaseWidget { } createAppointmentForm() { - const scheduler = { - getResourceById: () => this.resourceManager.resourceById, - getDataAccessors: () => this._dataAccessors, + const config: AppointmentFormConfig = { + dataAccessors: this._dataAccessors, + editing: this.editing, + resourceManager: this.resourceManager, + firstDayOfWeek: this.getFirstDayOfWeek(), + startDayHour: this.option('startDayHour') ?? 0, // @ts-expect-error createComponent: (element, component, options) => this._createComponent(element, component, options), - - getEditingConfig: () => this.editing, - getResourceManager: () => this.resourceManager, - - getFirstDayOfWeek: () => this.option('firstDayOfWeek'), - getStartDayHour: () => this.option('startDayHour'), getCalculatedEndDate: (startDateWithStartHour) => this._workSpace.calculateEndDate(startDateWithStartHour), - getTimeZoneCalculator: () => this.timeZoneCalculator, }; - return new AppointmentForm(scheduler); + return new AppointmentForm(config); } createAppointmentPopup(form) {