Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,68 @@ test('DataGrid – Resize indicator is moved when resizing a grouped column if s
});
});

// T1329677
test('DataGrid - column width changed via columnOption should be applied immediately, without a repaint (T1329677)', async (t) => {
const dataGrid = new DataGrid('#container');

await t.expect(dataGrid.isReady()).ok();

await dataGrid.apiColumnOption('Task_Assigned_Employee_ID', 'width', 700);

const assignedColumnWidth = await dataGrid.getHeaders().getHeaderRow(0)
.getHeaderCell(1).element.clientWidth;

await t
.expect(assignedColumnWidth)
.within(
700 - 1,
700 + 1,
'columnOption width should be applied immediately, without an explicit repaint',
);
}).before(async () => {
await createWidget('dxDataGrid', {
dataSource: [{ Task_Subject: 'Test' }],
columnAutoWidth: true,
columns: [
{ dataField: 'Task_Subject' },
{ dataField: 'Task_Assigned_Employee_ID', caption: 'Assigned' },
],
});
});

// T1329677
test('DataGrid - other column width should be updated immediately when another column width is changed via columnOption (T1329677)', async (t) => {
const dataGrid = new DataGrid('#container');

await t.expect(dataGrid.isReady()).ok();

const firstColumnOldWidth = await dataGrid.getDataCell(0, 0).element.clientWidth;

await dataGrid.apiColumnOption('Col2', 'width', 200);

const firstColumnNewWidth = await dataGrid.getDataCell(0, 0).element.clientWidth;

await t
.expect(firstColumnOldWidth).notEql(firstColumnNewWidth, 'first column width should be changed');
}).before(async () => {
await createWidget('dxDataGrid', {
dataSource: [{
Col1: 'Test 1',
Col2: 'Test 2',
Col3: 'Test 3',
Col4: 'Test 4',
}],
width: 400,
columnAutoWidth: true,
columns: [
{ dataField: 'Col1' },
{ dataField: 'Col2' },
{ dataField: 'Col3' },
{ dataField: 'Col4' },
],
});
});

const tryResizeHeaderInBandArea = (
dataGrid: DataGrid,
columnIndex: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ interface IndexedColumns {
negativeIndexedColumns: Record<string, Column[]>[];
}

export interface ColumnDimensionsUpdate {
columnIndex: Column['index'];
visibleWidth?: Column['visibleWidth'] | null;
width: Column['width'];
}

export class ColumnsController extends modules.Controller {
public _skipProcessingColumnsChange: any;

Expand Down Expand Up @@ -1488,6 +1494,41 @@ export class ColumnsController extends modules.Controller {
return this._columns ? this._columns.length : 0;
}

/** Applies dimensions already resolved by an internal layout operation. */
public updateColumnDimensions(updates: ColumnDimensionsUpdate[]): void {
if (!updates.length) {
return;
}

const columnsByIndex = new Map<Column['index'], Column>();

this._columns.concat(this._commandColumns).forEach((column: Column) => {
if (!columnsByIndex.has(column.index)) {
columnsByIndex.set(column.index, column);
}
});

this.beginUpdate();
try {
updates.forEach((dimensions) => {
const column = columnsByIndex.get(dimensions.columnIndex);

if (!column) {
return;
}

if (Object.prototype.hasOwnProperty.call(dimensions, 'visibleWidth')) {
columnOptionCore(this, column, 'visibleWidth', dimensions.visibleWidth);
}
columnOptionCore(this, column, 'width', dimensions.width, {
invalidateVisibleWidths: false,
});
});
} finally {
this.endUpdate();
}
}

public columnOption(identifier, option?, value?, notFireEvent?) {
const that = this;
const columns = that._columns.concat(that._commandColumns);
Expand All @@ -1501,10 +1542,10 @@ export class ColumnsController extends modules.Controller {
if (arguments.length === 2) {
return columnOptionCore(that, column, option);
}
columnOptionCore(that, column, option, value, notFireEvent);
columnOptionCore(that, column, option, value, { notFireEvent });
} else if (isObject(option)) {
each(option, (optionName, optionValue) => {
columnOptionCore(that, column, optionName, optionValue, notFireEvent);
columnOptionCore(that, column, optionName, optionValue, { notFireEvent });
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -715,9 +715,52 @@ export const fireOptionChanged = function (that: ColumnsController, options) {
}
};

export const columnOptionCore = function (that: ColumnsController, column, optionName, value?, notFireEvent?) {
const isVisibleWidthChangePendingForColumn = (that: ColumnsController, columnIndex): boolean => {
const columnChanges = that._columnChanges;

if (!columnChanges?.optionNames?.visibleWidth) {
return false;
}

return columnChanges.columnIndex === columnIndex
|| !!columnChanges.columnIndices?.includes(columnIndex);
};

const invalidateStaleVisibleWidths = (that: ColumnsController, changedColumn): void => {
if (isDefined(changedColumn.visibleWidth)
&& !isVisibleWidthChangePendingForColumn(that, changedColumn.index)) {
changedColumn.visibleWidth = null;
}

that._columns.forEach((column) => {
const hasCalculatedVisibleWidth = isNumeric(column.visibleWidth)
&& (!isDefined(column.width) || column.width === 'auto');
const shouldInvalidateVisibleWidth = column !== changedColumn
&& hasCalculatedVisibleWidth
&& !isVisibleWidthChangePendingForColumn(that, column.index);

if (shouldInvalidateVisibleWidth) {
column.visibleWidth = null;
}
});
};

interface ColumnOptionCoreOptions {
invalidateVisibleWidths?: boolean;
notFireEvent?: boolean;
}

export const columnOptionCore = function (
that: ColumnsController,
column,
optionName,
value?,
options: ColumnOptionCoreOptions = {},
) {
const optionGetter = compileGetter(optionName);
const columnIndex = column.index;
const { invalidateVisibleWidths = true } = options;
let { notFireEvent } = options;
let columns;
let changeType;
let initialColumn;
Expand All @@ -740,6 +783,10 @@ export const columnOptionCore = function (that: ColumnsController, column, optio
changeType = 'columns';
}

if (optionName === 'width' && invalidateVisibleWidths) {
invalidateStaleVisibleWidths(that, column);
}

const optionSetter = compileSetter(optionName);
// @ts-expect-error
optionSetter(column, value, { functionsAsIs: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view';

import type { ColumnChooserView } from '../column_chooser/m_column_chooser';
import type { ColumnHeadersView } from '../column_headers/m_column_headers';
import type { ColumnsController } from '../columns_controller/m_columns_controller';
import type { ColumnDimensionsUpdate, ColumnsController } from '../columns_controller/m_columns_controller';
import type { HeaderPanel } from '../header_panel/m_header_panel';
import modules from '../m_modules';
import gridCoreUtils from '../m_utils';
Expand Down Expand Up @@ -1081,21 +1081,23 @@ export class ColumnsResizerViewController extends modules.ViewController {
return isString(width) && width.endsWith('%');
}

function setColumnWidth(column, columnWidth, contentWidth, adaptColumnWidthByRatio) {
function getColumnDimensions(column, columnWidth, contentWidth, adaptColumnWidthByRatio): ColumnDimensionsUpdate | undefined {
if (column) {
const oldColumnWidth = column.width;
if (oldColumnWidth) {
adaptColumnWidthByRatio = isPercentWidth(oldColumnWidth);
}

if (adaptColumnWidthByRatio) {
columnsController.columnOption(column.index, 'visibleWidth', columnWidth);
columnsController.columnOption(column.index, 'width', `${(columnWidth / contentWidth * 100).toFixed(3)}%`);
} else {
columnsController.columnOption(column.index, 'visibleWidth', null);
columnsController.columnOption(column.index, 'width', columnWidth);
}
return {
columnIndex: column.index,
visibleWidth: adaptColumnWidthByRatio ? columnWidth : null,
width: adaptColumnWidthByRatio
? `${(columnWidth / contentWidth * 100).toFixed(3)}%`
: columnWidth,
};
}

return undefined;
}

function correctContentWidth(contentWidth, visibleColumns) {
Expand Down Expand Up @@ -1168,12 +1170,19 @@ export class ColumnsResizerViewController extends modules.ViewController {

contentWidth = correctContentWidth(contentWidth, visibleColumns);

setColumnWidth(column, cellWidth, contentWidth, adaptColumnWidthByRatio);
const columnDimensions = getColumnDimensions(column, cellWidth, contentWidth, adaptColumnWidthByRatio);

if (isNextColumnMode) {
nextCellWidth = Math.floor(nextCellWidth);
setColumnWidth(nextColumn, nextCellWidth, contentWidth, adaptColumnWidthByRatio);
const nextColumnDimensions = getColumnDimensions(nextColumn, nextCellWidth, contentWidth, adaptColumnWidthByRatio);
const updates = [columnDimensions, nextColumnDimensions].filter(isDefined);

columnsController.updateColumnDimensions(updates);
} else {
if (columnDimensions) {
columnsController.updateColumnDimensions([columnDimensions]);
}

const columnWidths = this._columnHeadersView.getColumnWidths();
columnWidths[resizingInfo.currentColumnIndex] = cellWidth;
const hasScroll = columnWidths.reduce((totalWidth, width) => totalWidth + width, 0) > this._rowsView.contentWidth();
Expand All @@ -1183,11 +1192,19 @@ export class ColumnsResizerViewController extends modules.ViewController {
columnsController.columnOption(visibleColumns[lastColumnIndex].index, 'visibleWidth', 'auto');
}
}

const updates: ColumnDimensionsUpdate[] = [];

for (let i = 0; i < columnWidths.length; i++) {
if (visibleColumns[i] && visibleColumns[i] !== column && visibleColumns[i].width === undefined) {
columnsController.columnOption(visibleColumns[i].index, 'width', columnWidths[i]);
updates.push({
columnIndex: visibleColumns[i].index,
width: columnWidths[i],
});
}
}

columnsController.updateColumnDimensions(updates);
}

columnsController.endUpdate();
Expand Down
9 changes: 9 additions & 0 deletions packages/devextreme/testing/helpers/gridBaseMocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,15 @@ module.exports = function($, gridCore, columnResizingReordering, domUtils, commo
}
},

updateColumnDimensions: function(updates) {
updates.forEach((dimensions) => {
if(Object.prototype.hasOwnProperty.call(dimensions, 'visibleWidth')) {
this.columnOption(dimensions.columnIndex, 'visibleWidth', dimensions.visibleWidth);
}
this.columnOption(dimensions.columnIndex, 'width', dimensions.width);
});
},

addCommandColumn: function(commandColumn) {
columns && columns.forEach(function(column, index) {
if(column.command === commandColumn.command) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,76 @@ QUnit.module('Column Resizing', baseModuleConfig, () => {
assert.equal($(rowsCols[2]).css('width'), '50px', 'width of three column - rows view');
});

// T1329677
QUnit.test('Calculated widths of unrelated columns should not be invalidated during repeated column resizing in widget mode', function(assert) {
const dataGrid = $('#dataGrid').dxDataGrid({
width: 500,
allowColumnResizing: true,
columnResizingMode: 'widget',
columnAutoWidth: true,
loadingTimeout: null,
dataSource: [{
firstName: 'John',
lastName: 'Smith',
room: '101',
birthDay: '2000-01-01',
position: 'Developer'
}],
columns: ['firstName', 'lastName', 'room', 'birthDay', 'position']
});
const instance = dataGrid.dxDataGrid('instance');
const columnsController = instance.getController('columns');
const untouchedColumns = columnsController.getColumns().slice(1, -1);
const resizeController = instance.getController('columnsResizer');
const moveSeparator = (pageX) => resizeController._moveSeparator({
event: {
data: resizeController,
type: 'mousemove',
pageX,
preventDefault: commonUtils.noop
}
});
const initialResizedColumnWidth = instance.columnOption(0, 'width');

assert.ok(
untouchedColumns.every((column) => Number.isFinite(column.visibleWidth)),
'untouched columns have calculated visible widths'
);

const invalidatedColumnIndexes = [];
untouchedColumns.forEach((column) => {
let visibleWidth = column.visibleWidth;

Object.defineProperty(column, 'visibleWidth', {
configurable: true,
enumerable: true,
get: () => visibleWidth,
set: (value) => {
visibleWidth = value;

if(value === null) {
invalidatedColumnIndexes.push(column.index);
}
}
});
});

resizeController._isResizing = true;
resizeController._targetPoint = { columnIndex: 0 };
resizeController._setupResizingInfo(-9900);

moveSeparator(-9880);

const resizedColumnWidth = instance.columnOption(0, 'width');

assert.notEqual(resizedColumnWidth, initialResizedColumnWidth, 'first mouse move is processed');

moveSeparator(-9870);

assert.notEqual(instance.columnOption(0, 'width'), resizedColumnWidth, 'second mouse move is processed');
assert.deepEqual(invalidatedColumnIndexes, [], 'unrelated visible widths are not invalidated');
});

// T804582
QUnit.test('Cursor should switch style when it was moved to columns separator if grid has only one row and big header panel', function(assert) {
const dataGrid = $('#dataGrid').dxDataGrid({
Expand Down
Loading
Loading