Skip to content

Commit

Permalink
Scheduler(T1201196): Fix nested expressions issue. (#26320) (#26350)
Browse files Browse the repository at this point in the history
  • Loading branch information
williamvinogradov authored Dec 25, 2023
1 parent 0366902 commit 655672c
Show file tree
Hide file tree
Showing 5 changed files with 702 additions and 89 deletions.
171 changes: 118 additions & 53 deletions packages/devextreme/js/__internal/scheduler/appointment_popup/m_form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import messageLocalization from '@js/localization/message';
import { Semaphore } from '@js/renovation/ui/scheduler/utils/semaphore/semaphore';
import Form from '@js/ui/form';
import { current, isFluent } from '@js/ui/themes';
import { ExpressionUtils } from '@ts/scheduler/m_expression_utils';

import { createAppointmentAdapter } from '../m_appointment_adapter';
import timeZoneDataUtils from '../timezones/m_utils_timezones_data';
Expand All @@ -25,6 +26,21 @@ export const APPOINTMENT_FORM_GROUP_NAMES = {
Recurrence: 'recurrenceGroup',
};

// TODO: Remove duplication in the scheduler's popup testing model.
// NOTE: These CSS classes allow access the editors
// from e2e testcafe tests.
const E2E_TEST_CLASSES = {
form: 'e2e-dx-scheduler-form',
textEditor: 'e2e-dx-scheduler-form-text',
descriptionEditor: 'e2e-dx-scheduler-form-description',
startDateEditor: 'e2e-dx-scheduler-form-start-date',
endDateEditor: 'e2e-dx-scheduler-form-end-date',
startDateTimeZoneEditor: 'e2e-dx-scheduler-form-start-date-timezone',
endDateTimeZoneEditor: 'e2e-dx-scheduler-form-end-date-timezone',
allDaySwitch: 'e2e-dx-scheduler-form-all-day-switch',
recurrenceSwitch: 'e2e-dx-scheduler-form-recurrence-switch',
};

const getStylingModeFunc = (): string | undefined => (isFluent(current()) ? 'filled' : undefined);

const getStartDateWithStartHour = (startDate, startDayHour) => new Date(new Date(startDate).setHours(startDayHour));
Expand All @@ -43,27 +59,6 @@ const updateRecurrenceItemVisibility = (recurrenceRuleExpr, value, form) => {
form.getEditor(recurrenceRuleExpr)?.changeValueByVisibility(value);
};

const createDateBoxEditor = (dataField, colSpan, firstDayOfWeek, label, onValueChanged) => ({
editorType: 'dxDateBox',
dataField,
colSpan,
label: {
text: messageLocalization.format(label),
},
validationRules: [{
type: 'required',
}],
editorOptions: {
stylingMode: getStylingModeFunc(),
width: '100%',
calendarOptions: {
firstDayOfWeek,
},
onValueChanged,
useMaskBehavior: true,
},
});

export class AppointmentForm {
scheduler: any;

Expand Down Expand Up @@ -100,17 +95,19 @@ export class AppointmentForm {

create(triggerResize, changeSize, formData) {
const { allowTimeZoneEditing } = this.scheduler.getEditingConfig();
const { expr } = this.scheduler.getDataAccessors();
const dataAccessors = this.scheduler.getDataAccessors();
const { expr } = dataAccessors;

const recurrenceEditorVisibility = !!formData[expr.recurrenceRuleExpr]; // TODO
const colSpan = recurrenceEditorVisibility ? 1 : 2;
const isRecurrence = !!ExpressionUtils.getField(dataAccessors, 'recurrenceRule', formData);
const colSpan = isRecurrence ? 1 : 2;

const mainItems = [
...this._createMainItems(expr, triggerResize, changeSize, allowTimeZoneEditing),
...this.scheduler.createResourceEditorModel(),
];

changeSize(recurrenceEditorVisibility);
changeSize(isRecurrence);

const items = [
{
itemType: 'group',
Expand All @@ -124,7 +121,7 @@ export class AppointmentForm {
}, {
itemType: 'group',
name: APPOINTMENT_FORM_GROUP_NAMES.Recurrence,
visible: recurrenceEditorVisibility,
visible: isRecurrence,
colSpan,
items: this._createRecurrenceEditor(expr),
},
Expand Down Expand Up @@ -167,6 +164,9 @@ export class AppointmentForm {
}
},
screenByWidth: (width) => (width < SCREEN_SIZE_OF_SINGLE_COLUMN || devices.current().deviceType !== 'desktop' ? 'xs' : 'lg'),
elementAttr: {
class: E2E_TEST_CLASSES.form,
},
});
}

Expand All @@ -192,20 +192,23 @@ export class AppointmentForm {
const previousValue = dateSerialization.deserializeDate(args.previousValue);
const dateEditor = this.form.getEditor(dateExpr);
const dateValue = dateSerialization.deserializeDate(dateEditor.option('value'));

if (this.semaphore.isFree() && dateValue && value && isNeedCorrect(dateValue, value)) {
const duration = previousValue ? dateValue.getTime() - previousValue.getTime() : 0;
dateEditor.option('value', new Date(value.getTime() + duration));
}
}

_createTimezoneEditor(timeZoneExpr, secondTimeZoneExpr, visibleIndex, colSpan, isMainTimeZone, visible = false) {
_createTimezoneEditor(timeZoneExpr, secondTimeZoneExpr, visibleIndex, colSpan, isMainTimeZone, cssClass, visible = false) {
const noTzTitle = messageLocalization.format('dxScheduler-noTimezoneTitle');

return {
name: this.normalizeEditorName(timeZoneExpr),
dataField: timeZoneExpr,
editorType: 'dxSelectBox',
visibleIndex,
colSpan,
cssClass,
label: {
text: ' ',
},
Expand All @@ -231,46 +234,67 @@ export class AppointmentForm {
const firstDayOfWeek = this.scheduler.getFirstDayOfWeek();

return [
createDateBoxEditor(
this.createDateBoxEditor(
dataExprs.startDateExpr,
colSpan,
firstDayOfWeek,
'dxScheduler-editorLabelStartDate',
E2E_TEST_CLASSES.startDateEditor,
(args) => {
this._dateBoxValueChanged(args, dataExprs.endDateExpr, (endValue, startValue) => endValue < startValue);
},
),

this._createTimezoneEditor(dataExprs.startDateTimeZoneExpr, dataExprs.endDateTimeZoneExpr, 1, colSpan, true, allowTimeZoneEditing),
this._createTimezoneEditor(
dataExprs.startDateTimeZoneExpr,
dataExprs.endDateTimeZoneExpr,
1,
colSpan,
true,
E2E_TEST_CLASSES.startDateTimeZoneEditor,
allowTimeZoneEditing,
),

createDateBoxEditor(
this.createDateBoxEditor(
dataExprs.endDateExpr,
colSpan,
firstDayOfWeek,
'dxScheduler-editorLabelEndDate',
E2E_TEST_CLASSES.endDateEditor,
(args) => {
this._dateBoxValueChanged(args, dataExprs.startDateExpr, (startValue, endValue) => endValue < startValue);
},
),

this._createTimezoneEditor(dataExprs.endDateTimeZoneExpr, dataExprs.startDateTimeZoneExpr, 3, colSpan, false, allowTimeZoneEditing),
this._createTimezoneEditor(
dataExprs.endDateTimeZoneExpr,
dataExprs.startDateTimeZoneExpr,
3,
colSpan,
false,
E2E_TEST_CLASSES.endDateTimeZoneEditor,
allowTimeZoneEditing,
),
];
}

_changeFormItemDateType(itemPath, isAllDay) {
const itemEditorOptions = this.form.itemOption(itemPath).editorOptions;
_changeFormItemDateType(name: string, groupName: string, isAllDay: boolean): void {
const editorPath = this.getEditorPath(name, groupName);
const itemEditorOptions = this.form.itemOption(editorPath).editorOptions;

const type = isAllDay ? 'date' : 'datetime';

const newEditorOption = { ...itemEditorOptions, type };

this.form.itemOption(itemPath, 'editorOptions', newEditorOption);
this.form.itemOption(editorPath, 'editorOptions', newEditorOption);
}

_createMainItems(dataExprs, triggerResize, changeSize, allowTimeZoneEditing) {
return [
{
name: this.normalizeEditorName(dataExprs.textExpr),
dataField: dataExprs.textExpr,
cssClass: E2E_TEST_CLASSES.textEditor,
editorType: 'dxTextBox',
colSpan: 2,
label: {
Expand All @@ -297,8 +321,9 @@ export class AppointmentForm {
xs: 2,
},
items: [{
name: this.normalizeEditorName(dataExprs.allDayExpr),
dataField: dataExprs.allDayExpr,
cssClass: 'dx-appointment-form-switch',
cssClass: `dx-appointment-form-switch ${E2E_TEST_CLASSES.allDaySwitch}`,
editorType: 'dxSwitch',
label: {
text: messageLocalization.format('dxScheduler-allDay'),
Expand All @@ -324,17 +349,14 @@ export class AppointmentForm {
}
}

const startDateItemPath = `${APPOINTMENT_FORM_GROUP_NAMES.Main}.${dataExprs.startDateExpr}`;
const endDateItemPath = `${APPOINTMENT_FORM_GROUP_NAMES.Main}.${dataExprs.endDateExpr}`;

this._changeFormItemDateType(startDateItemPath, value);
this._changeFormItemDateType(endDateItemPath, value);
this._changeFormItemDateType(dataExprs.startDateExpr, 'Main', value);
this._changeFormItemDateType(dataExprs.endDateExpr, 'Main', value);
},
},
}, {
editorType: 'dxSwitch',
dataField: 'repeat',
cssClass: 'dx-appointment-form-switch',
cssClass: `dx-appointment-form-switch ${E2E_TEST_CLASSES.recurrenceSwitch}`,
name: 'visibilityChanged',
label: {
text: messageLocalization.format('dxScheduler-editorLabelRecurrence'),
Expand All @@ -361,7 +383,9 @@ export class AppointmentForm {
colSpan: 2,
},
{
name: this.normalizeEditorName(dataExprs.descriptionExpr),
dataField: dataExprs.descriptionExpr,
cssClass: E2E_TEST_CLASSES.descriptionEditor,
editorType: 'dxTextArea',
colSpan: 2,
label: {
Expand All @@ -380,6 +404,7 @@ export class AppointmentForm {

_createRecurrenceEditor(dataExprs) {
return [{
name: this.normalizeEditorName(dataExprs.recurrenceRuleExpr),
dataField: dataExprs.recurrenceRuleExpr,
editorType: 'dxRecurrenceEditor',
editorOptions: {
Expand All @@ -397,8 +422,8 @@ export class AppointmentForm {
setEditorsType(allDay) {
const { startDateExpr, endDateExpr } = this.scheduler.getDataAccessors().expr;

const startDateItemPath = `${APPOINTMENT_FORM_GROUP_NAMES.Main}.${startDateExpr}`;
const endDateItemPath = `${APPOINTMENT_FORM_GROUP_NAMES.Main}.${endDateExpr}`;
const startDateItemPath = this.getEditorPath(startDateExpr, 'Main');
const endDateItemPath = this.getEditorPath(endDateExpr, 'Main');

const startDateFormItem = this.form.itemOption(startDateItemPath);
const endDateFormItem = this.form.itemOption(endDateItemPath);
Expand All @@ -421,36 +446,76 @@ export class AppointmentForm {
}

setEditorOptions(name, groupName: 'Main' | 'Recurrence', options) {
const editorPath = `${APPOINTMENT_FORM_GROUP_NAMES[groupName]}.${name}`;
const editorPath = this.getEditorPath(name, groupName);
const editor = this.form.itemOption(editorPath);

editor && this.form.itemOption(editorPath, 'editorOptions', extend({}, editor.editorOptions, options));
}

setTimeZoneEditorDataSource(date, path) {
setTimeZoneEditorDataSource(date, name) {
const dataSource = this.createTimeZoneDataSource(date);
this.setEditorOptions(path, 'Main', { dataSource });
this.setEditorOptions(name, 'Main', { dataSource });
}

updateFormData(formData) {
this.semaphore.take();

this.form.option('formData', formData);

const dataExprs = this.scheduler.getDataAccessors().expr;
const dataAccessors = this.scheduler.getDataAccessors();
const { expr } = dataAccessors;

const allDay = formData[dataExprs.allDayExpr];
const rawStartDate = ExpressionUtils.getField(dataAccessors, 'startDate', formData);
const rawEndDate = ExpressionUtils.getField(dataAccessors, 'endDate', formData);

const startDate = new Date(formData[dataExprs.startDateExpr]);
const endDate = new Date(formData[dataExprs.endDateExpr]);
const allDay = ExpressionUtils.getField(dataAccessors, 'allDay', formData);
const startDate = new Date(rawStartDate);
const endDate = new Date(rawEndDate);

this.setTimeZoneEditorDataSource(startDate, dataExprs.startDateTimeZoneExpr);
this.setTimeZoneEditorDataSource(endDate, dataExprs.endDateTimeZoneExpr);
this.setTimeZoneEditorDataSource(startDate, expr.startDateTimeZoneExpr);
this.setTimeZoneEditorDataSource(endDate, expr.endDateTimeZoneExpr);

this.updateRecurrenceEditorStartDate(startDate, dataExprs.recurrenceRuleExpr);
this.updateRecurrenceEditorStartDate(startDate, expr.recurrenceRuleExpr);

this.setEditorsType(allDay);

this.semaphore.release();
}

private createDateBoxEditor(dataField, colSpan, firstDayOfWeek, label, cssClass, onValueChanged) {
return {
editorType: 'dxDateBox',
name: this.normalizeEditorName(dataField),
dataField,
colSpan,
cssClass,
label: {
text: messageLocalization.format(label),
},
validationRules: [{
type: 'required',
}],
editorOptions: {
stylingMode: getStylingModeFunc(),
width: '100%',
calendarOptions: {
firstDayOfWeek,
},
onValueChanged,
useMaskBehavior: true,
},
};
}

private getEditorPath(name: string, groupName: string): string {
const normalizedName = this.normalizeEditorName(name);
return `${APPOINTMENT_FORM_GROUP_NAMES[groupName]}.${normalizedName}`;
}

private normalizeEditorName(name: string): string {
// NOTE: This ternary operator covers the "recurrenceRuleExpr: null/''" scenarios.
return name
? name.replace(/\./g, '_')
: name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isPopupFullScreenNeeded,
} from '@js/renovation/ui/scheduler/appointment_edit_form/popup_config';
import Popup from '@js/ui/popup/ui.popup';
import { ExpressionUtils } from '@ts/scheduler/m_expression_utils';

import { createAppointmentAdapter } from '../m_appointment_adapter';
import { hide as hideLoading, show as showLoading } from '../m_loading';
Expand Down Expand Up @@ -222,13 +223,12 @@ export class AppointmentPopup {
}

updatePopupFullScreenMode() {
if (this.form.dxForm) { // TODO
if (this.form.dxForm && this.visible) { // TODO
const { formData } = this.form;
const isRecurrence = formData[this.scheduler.getDataAccessors().expr.recurrenceRuleExpr];
const dataAccessors = this.scheduler.getDataAccessors();
const isRecurrence = ExpressionUtils.getField(dataAccessors, 'recurrenceRule', formData);

if (this.visible) {
this.changeSize(isRecurrence);
}
this.changeSize(isRecurrence);
}
}

Expand Down
Loading

0 comments on commit 655672c

Please sign in to comment.