Skip to content

Commit 9154cb7

Browse files
authored
CardView: column option updates (eQk77HTM) (DevExpress#29800)
1 parent 5850e96 commit 9154cb7

File tree

23 files changed

+591
-74
lines changed

23 files changed

+591
-74
lines changed

e2e/testcafe-devextreme/tests/cardView/sorting/behavior.themes.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ test('Default render', async (t) => {
3737
});
3838
});
3939

40-
// TODO: Unskip this test after columnOptions method fix
41-
// Now column options from options manager override internal columns state
42-
test.skip('Default multiple sorting render', async (t) => {
40+
test('Default multiple sorting render', async (t) => {
4341
const { takeScreenshot, compareResults } = createScreenshotsComparer(t);
4442
const cardView = new CardView('#container');
4543
await testScreenshot(t, takeScreenshot, 'cardview_headers_with_multiple_sorting_render.png', { element: cardView.element });

packages/devextreme/js/__internal/grids/new/grid_core/column_chooser/view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { View } from '../core/view';
1616
import { OptionsController } from '../options_controller/options_controller';
1717
import { ToolbarController } from '../toolbar/controller';
1818
import type { PredefinedToolbarItem } from '../toolbar/types';
19-
import { addWidgetPrefix } from '../utils';
19+
import { addWidgetPrefix } from '../utils/common';
2020
import type { ColumnChooserProps } from './column_chooser';
2121
import { CLASS, ColumnChooser } from './column_chooser';
2222
import { ColumnChooserController } from './controller';

packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.test.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,5 +244,200 @@ describe('ColumnsController', () => {
244244
{ dataField: 'c', visibleIndex: 0 },
245245
]);
246246
});
247+
248+
it('should not touch the option manager columns', () => {
249+
const { options, columnsController } = setup(
250+
{
251+
columns: ['a', 'b', 'c'],
252+
},
253+
);
254+
255+
const [col] = columnsController.columns.peek();
256+
columnsController.columnOption(col, 'sortOrder', 'desc');
257+
258+
const result = options.oneWay('columns').peek();
259+
expect(result).toStrictEqual(['a', 'b', 'c']);
260+
});
261+
262+
it('should update complex path with dots (nested options)', () => {
263+
const { columnsController } = setup(
264+
{
265+
columns: [{
266+
dataField: 'a',
267+
}],
268+
},
269+
);
270+
271+
const [col] = columnsController.columns.peek();
272+
// @ts-expect-error fix columnOption type
273+
columnsController.columnOption(col, 'headerFilter.search.enabled', true);
274+
275+
const [result] = columnsController.columns.peek();
276+
expect(result).toMatchObject({
277+
dataField: 'a',
278+
headerFilter: {
279+
search: {
280+
enabled: true,
281+
},
282+
},
283+
});
284+
});
285+
286+
it('should not be overridden by another column public option change', () => {
287+
const { options, columnsController } = setup(
288+
{
289+
columns: [{
290+
dataField: 'a',
291+
}],
292+
},
293+
);
294+
295+
const [col] = columnsController.columns.peek();
296+
columnsController.columnOption(col, 'sortOrder', 'asc');
297+
298+
const [resultFirst] = columnsController.columns.peek();
299+
expect(resultFirst).toMatchObject({
300+
dataField: 'a',
301+
sortOrder: 'asc',
302+
});
303+
304+
options.option('columns[0].sortIndex', 99);
305+
306+
const [resultSecond] = columnsController.columns.peek();
307+
expect(resultSecond).toMatchObject({
308+
dataField: 'a',
309+
sortOrder: 'asc',
310+
});
311+
});
312+
313+
it('should be overridden by same column public option change', () => {
314+
const { options, columnsController } = setup(
315+
{
316+
columns: [{
317+
dataField: 'a',
318+
}],
319+
},
320+
);
321+
322+
const [col] = columnsController.columns.peek();
323+
columnsController.columnOption(col, 'sortOrder', 'asc');
324+
325+
const [resultFirst] = columnsController.columns.peek();
326+
expect(resultFirst).toMatchObject({
327+
dataField: 'a',
328+
sortOrder: 'asc',
329+
});
330+
331+
options.option('columns[0].sortOrder', 'desc');
332+
333+
const [resultSecond] = columnsController.columns.peek();
334+
expect(resultSecond).toMatchObject({
335+
dataField: 'a',
336+
sortOrder: 'desc',
337+
});
338+
});
339+
340+
it('should be overridden by parent column public option change', () => {
341+
const { options, columnsController } = setup(
342+
{
343+
columns: [{
344+
dataField: 'a',
345+
}],
346+
},
347+
);
348+
349+
const [col] = columnsController.columns.peek();
350+
// @ts-expect-error fix columnOption type
351+
columnsController.columnOption(col, 'headerFilter.allowSelectAll', false);
352+
353+
const [resultFirst] = columnsController.columns.peek();
354+
expect(resultFirst).toMatchObject({
355+
dataField: 'a',
356+
headerFilter: { allowSelectAll: false },
357+
});
358+
359+
options.option('columns[0].headerFilter', { height: 300 });
360+
361+
const [resultSecond] = columnsController.columns.peek();
362+
expect(resultSecond).toMatchObject({
363+
dataField: 'a',
364+
headerFilter: { height: 300 },
365+
});
366+
});
367+
368+
it('should be overridden by whole column array public option change', () => {
369+
const { options, columnsController } = setup(
370+
{
371+
columns: ['a'],
372+
},
373+
);
374+
375+
const [col] = columnsController.columns.peek();
376+
columnsController.columnOption(col, 'sortIndex', 35);
377+
378+
const [resultFirst] = columnsController.columns.peek();
379+
expect(resultFirst).toMatchObject({
380+
dataField: 'a',
381+
sortIndex: 35,
382+
});
383+
384+
options.option('columns', ['a', 'b']);
385+
386+
const [resultSecond, resultThird] = columnsController.columns.peek();
387+
expect(resultSecond.dataField).toBe('a');
388+
expect(resultSecond.sortIndex).toBeUndefined();
389+
expect(resultThird.dataField).toBe('b');
390+
expect(resultThird.sortIndex).toBeUndefined();
391+
});
392+
393+
it('should be overridden by specific column public option change', () => {
394+
const { options, columnsController } = setup(
395+
{
396+
columns: ['a'],
397+
},
398+
);
399+
400+
const [col] = columnsController.columns.peek();
401+
columnsController.columnOption(col, 'sortIndex', 35);
402+
403+
const [resultFirst] = columnsController.columns.peek();
404+
expect(resultFirst).toMatchObject({
405+
dataField: 'a',
406+
sortIndex: 35,
407+
});
408+
409+
options.option('columns[0]', { dataField: 'b' });
410+
411+
const [resultSecond] = columnsController.columns.peek();
412+
expect(resultSecond.dataField).toBe('b');
413+
expect(resultSecond.sortIndex).toBeUndefined();
414+
});
415+
416+
it('should not be overridden by another column public option change', () => {
417+
const { options, columnsController } = setup(
418+
{
419+
columns: ['a', 'b'],
420+
},
421+
);
422+
423+
const [col] = columnsController.columns.peek();
424+
columnsController.columnOption(col, 'sortIndex', 35);
425+
426+
const [resultFirst] = columnsController.columns.peek();
427+
expect(resultFirst).toMatchObject({
428+
dataField: 'a',
429+
sortIndex: 35,
430+
});
431+
432+
options.option('columns[1]', { dataField: 'b', sortOrder: 'asc' });
433+
434+
const [resultSecond, resultThird] = columnsController.columns.peek();
435+
expect(resultSecond.dataField).toBe('a');
436+
expect(resultSecond.sortOrder).toBeUndefined();
437+
expect(resultSecond.sortIndex).toBe(35);
438+
expect(resultThird.dataField).toBe('b');
439+
expect(resultThird.sortOrder).toBe('asc');
440+
expect(resultThird.sortIndex).toBeUndefined();
441+
});
247442
});
248443
});

packages/devextreme/js/__internal/grids/new/grid_core/columns_controller/columns_controller.ts

Lines changed: 33 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,30 @@ import { computed, effect, signal } from '@preact/signals-core';
33
import type { DataObject } from '@ts/grids/new/grid_core/data_controller/types';
44
import type { HeaderFilterRootOptions } from '@ts/grids/new/grid_core/filtering/header_filter/index';
55
import { mergeColumnHeaderFilterOptions } from '@ts/grids/new/grid_core/filtering/header_filter/utils';
6+
import type { OptionWithChanges } from '@ts/grids/new/grid_core/options_controller/types';
67

78
import { OptionsController } from '../options_controller/options_controller';
9+
import { updateColumnSettings } from './columns_settings/index';
810
import type { ColumnProperties, ColumnSettings, PreNormalizedColumn } from './options';
911
import type { Column, ColumnsConfigurationFromData, VisibleColumn } from './types';
1012
import {
13+
columnOptionUpdate,
1114
getColumnIndexByName,
1215
getColumnOptionsFromDataItem,
1316
normalizeColumns,
14-
normalizeVisibleIndexes,
17+
normalizeColumnsVisibleIndexes,
1518
preNormalizeColumns,
1619
} from './utils';
1720

1821
export class ColumnsController {
19-
private readonly columnsConfiguration: ReadonlySignal<ColumnProperties[] | undefined | null>;
20-
2122
private readonly headerFilterConfiguration: ReadonlySignal<HeaderFilterRootOptions | undefined>;
2223

2324
private readonly columnsSettings: Signal<PreNormalizedColumn[]>;
2425

26+
private readonly columnsConfiguration: ReadonlySignal<
27+
OptionWithChanges<ColumnProperties[] | undefined | null>
28+
>;
29+
2530
private readonly columnsConfigurationFromData: Signal<ColumnsConfigurationFromData | null>;
2631

2732
public readonly columns: ReadonlySignal<Column[]>;
@@ -37,17 +42,25 @@ export class ColumnsController {
3742
constructor(
3843
private readonly options: OptionsController,
3944
) {
40-
this.columnsConfiguration = this.options.oneWay('columns');
45+
this.columnsConfiguration = this.options.oneWayWithChanges('columns');
4146
this.headerFilterConfiguration = this.options.oneWay('headerFilter');
4247

43-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44-
this.columnsSettings = signal(undefined as any);
48+
this.columnsSettings = signal([]);
4549
this.columnsConfigurationFromData = signal<
4650
ColumnsConfigurationFromData | null
4751
>(null);
4852

4953
effect(() => {
50-
const columnsConfigurationFromOptions = this.columnsConfiguration.value;
54+
const settings = this.columnsSettings.peek() ?? [];
55+
const { value: columnsConfigurationFromOptions, changes } = this.columnsConfiguration.value;
56+
57+
const newSettings = updateColumnSettings(settings, changes);
58+
59+
if (newSettings.length !== 0) {
60+
this.columnsSettings.value = newSettings;
61+
return;
62+
}
63+
5164
const columnsConfigurationFromData = this.columnsConfigurationFromData.value?.dataFields;
5265
const columnsConfiguration = columnsConfigurationFromOptions
5366
?? columnsConfigurationFromData
@@ -102,54 +115,29 @@ export class ColumnsController {
102115
}
103116

104117
public columnOption<TProp extends keyof ColumnSettings>(
105-
column: Column,
118+
{ name }: Column,
119+
// TODO: Fix type -> option may be path with dots in runtime
120+
// E.g: 'columnOption('A', 'headerFilter.search.enabled', true)
106121
option: TProp,
107122
value: ColumnSettings[TProp],
108123
): void {
109-
const columns = this.columnsSettings.peek();
110-
const index = getColumnIndexByName(columns, column.name);
111-
112-
if (columns[index][option] === value) {
113-
this.columnsSettings.value = columns;
114-
return;
115-
}
116-
117-
let newColumns = [...columns];
118-
119-
newColumns[index] = {
120-
...newColumns[index],
121-
[option]: value,
122-
};
123-
124-
newColumns = this.normalizeColumnsVisibleIndexes(newColumns, index);
125-
126-
this.columnsSettings.value = newColumns;
124+
const settings = this.columnsSettings.peek();
125+
const columnIdx = getColumnIndexByName(settings, name);
126+
127+
this.columnsSettings.value = columnOptionUpdate(
128+
settings,
129+
columnIdx,
130+
option,
131+
value,
132+
);
127133
}
128134

129135
public updateColumns(func: (columns: PreNormalizedColumn[]) => PreNormalizedColumn[]): void {
130136
let newColumnSettings = func(this.columnsSettings.peek());
131-
newColumnSettings = this.normalizeColumnsVisibleIndexes(newColumnSettings);
137+
newColumnSettings = normalizeColumnsVisibleIndexes(newColumnSettings);
132138
this.columnsSettings.value = newColumnSettings;
133139
}
134140

135-
private normalizeColumnsVisibleIndexes(
136-
columns: PreNormalizedColumn[],
137-
forceIndex?: number,
138-
): PreNormalizedColumn[] {
139-
const result = [...columns];
140-
141-
const visibleIndexes = normalizeVisibleIndexes(
142-
columns.map((c) => c.visibleIndex),
143-
forceIndex,
144-
);
145-
146-
visibleIndexes.forEach((visibleIndex, i) => {
147-
result[i].visibleIndex = visibleIndex;
148-
});
149-
150-
return result;
151-
}
152-
153141
public setColumnOptionsFromDataItem(
154142
item: DataObject,
155143
): void {

0 commit comments

Comments
 (0)