Skip to content

Commit

Permalink
DataGrid - Prevents focusing hidden cells (T887014) (#13050)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickMitrokhin authored May 18, 2020
1 parent 48c758d commit 87a3040
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 29 deletions.
49 changes: 33 additions & 16 deletions js/ui/grid_core/ui.grid_core.adaptivity.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,40 +359,48 @@ const AdaptiveColumnsController = modules.ViewController.inherit({
const visibleIndex = this._getAdaptiveColumnVisibleIndex(visibleColumns);
if(typeUtils.isDefined(visibleIndex)) {
resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH;
this._addCssClassToColumn(COMMAND_ADAPTIVE_HIDDEN_CLASS, visibleIndex);
this._hideVisibleColumn({ isCommandColumn: true, visibleIndex });
}
},

_removeCssClassFromColumn: function(cssClassName) {
let $cells;
_showHiddenCellsInView: function({ $cells, isCommandColumn }) {
const cssClassNameToRemove = isCommandColumn ? COMMAND_ADAPTIVE_HIDDEN_CLASS : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS);
$cells.removeClass(cssClassNameToRemove);
},

_showHiddenColumns: function() {
for(let i = 0; i < COLUMN_VIEWS.length; i++) {
const view = this.getView(COLUMN_VIEWS[i]);
if(view && view.isVisible() && view.element()) {
$cells = view.element().find('.' + cssClassName);
$cells.removeClass(cssClassName);
const viewName = view.name;
const $hiddenCommandCells = view.element().find('.' + COMMAND_ADAPTIVE_HIDDEN_CLASS);
this._showHiddenCellsInView({
viewName,
$cells: $hiddenCommandCells,
isCommandColumn: true
});
const $hiddenCells = view.element().find('.' + this.addWidgetPrefix(HIDDEN_COLUMN_CLASS));
this._showHiddenCellsInView({
viewName,
$cells: $hiddenCells
});
}
}
},

_removeCssClassesFromColumns: function() {
this._removeCssClassFromColumn(COMMAND_ADAPTIVE_HIDDEN_CLASS);
this._removeCssClassFromColumn(this.addWidgetPrefix(HIDDEN_COLUMN_CLASS));
},

_isCellValid: function($cell) {
return $cell && $cell.length && !$cell.hasClass(MASTER_DETAIL_CELL_CLASS) && !$cell.hasClass(GROUP_CELL_CLASS);
},

_addCssClassToColumn: function(cssClassName, visibleIndex) {
_hideVisibleColumn: function({ isCommandColumn, visibleIndex }) {
const that = this;
COLUMN_VIEWS.forEach(function(viewName) {
const view = that.getView(viewName);
view && that._addCssClassToViewColumn(view, cssClassName, visibleIndex);
view && that._hideVisibleColumnInView({ view, isCommandColumn, visibleIndex });
});
},

_addCssClassToViewColumn: function(view, cssClassName, visibleIndex) {
_hideVisibleColumnInView: function({ view, isCommandColumn, visibleIndex }) {
const viewName = view.name;
let $cellElement;
const column = this._columnsController.getVisibleColumns()[visibleIndex];
Expand All @@ -408,13 +416,22 @@ const AdaptiveColumnsController = modules.ViewController.inherit({
const currentVisibleIndex = viewName === COLUMN_HEADERS_VIEW ? this._columnsController.getVisibleIndex(column.index, rowIndex) : visibleIndex;
if(currentVisibleIndex >= 0) {
$cellElement = $rowElements.eq(rowIndex).children().eq(currentVisibleIndex);
this._isCellValid($cellElement) && $cellElement.addClass(cssClassName);
this._isCellValid($cellElement) && this._hideVisibleCellInView({
viewName,
isCommandColumn,
$cell: $cellElement,
});
}
}
}
}
},

_hideVisibleCellInView: function({ $cell, isCommandColumn }) {
const cssClassNameToAdd = isCommandColumn ? COMMAND_ADAPTIVE_HIDDEN_CLASS : this.addWidgetPrefix(HIDDEN_COLUMN_CLASS);
$cell.addClass(cssClassNameToAdd);
},

_getEditMode: function() {
return this._editingController.getEditMode();
},
Expand Down Expand Up @@ -485,7 +502,7 @@ const AdaptiveColumnsController = modules.ViewController.inherit({

rootElementWidth += that._calculateColumnWidth(column, rootElementWidth, visibleContentColumns, columnsCanFit);

that._addCssClassToColumn(that.addWidgetPrefix(HIDDEN_COLUMN_CLASS), visibleIndex);
that._hideVisibleColumn({ visibleIndex });
resultWidths[visibleIndex] = HIDDEN_COLUMNS_WIDTH;
this._hiddenColumns.push(column);
visibleContentColumns = getVisibleContentColumns();
Expand Down Expand Up @@ -975,7 +992,7 @@ module.exports = {
},

_toggleBestFitMode: function(isBestFit) {
isBestFit && this._adaptiveColumnsController._removeCssClassesFromColumns();
isBestFit && this._adaptiveColumnsController._showHiddenColumns();
this.callBase(isBestFit);
},

Expand Down
25 changes: 25 additions & 0 deletions js/ui/grid_core/ui.grid_core.keyboard_navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const GROUP_FOOTER_CLASS = 'group-footer';
const ROW_CLASS = 'dx-row';
const DATA_ROW_CLASS = 'dx-data-row';
const GROUP_ROW_CLASS = 'dx-group-row';
const HEADER_ROW_CLASS = 'dx-header-row';
const EDIT_FORM_ITEM_CLASS = 'edit-form-item';
const MASTER_DETAIL_ROW_CLASS = 'dx-master-detail-row';
const FREESPACE_ROW_CLASS = 'dx-freespace-row';
Expand Down Expand Up @@ -50,6 +51,9 @@ const EDIT_MODE_CELL = 'cell';
const FOCUS_TYPE_ROW = 'row';
const FOCUS_TYPE_CELL = 'cell';

const COLUMN_HEADERS_VIEW = 'columnHeadersView';


function isGroupRow($row) {
return $row && $row.hasClass(GROUP_ROW_CLASS);
}
Expand Down Expand Up @@ -82,6 +86,10 @@ function isMobile() {
return devices.current().deviceType !== 'desktop';
}

function isCellInHeaderRow($cell) {
return !!$cell.parent(`.${HEADER_ROW_CLASS}`).length;
}

const KeyboardNavigationController = core.ViewController.inherit({
// #region Initialization
init: function() {
Expand Down Expand Up @@ -2014,6 +2022,23 @@ module.exports = {
}
}
}
},
adaptiveColumns: {
_showHiddenCellsInView: function({ viewName, $cells, isCommandColumn }) {
this.callBase.apply(this, arguments);

viewName === COLUMN_HEADERS_VIEW && !isCommandColumn && $cells.each((_, cellElement) => {
const $cell = $(cellElement);
isCellInHeaderRow($cell) && $cell.attr('tabindex', 0);
});
},
_hideVisibleCellInView: function({ viewName, $cell, isCommandColumn }) {
this.callBase.apply(this, arguments);

if(viewName === COLUMN_HEADERS_VIEW && !isCommandColumn && isCellInHeaderRow($cell)) {
$cell.removeAttr('tabindex');
}
}
}
}
}
Expand Down
41 changes: 28 additions & 13 deletions testing/functional/model/dataGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const CLASS = {
commandEdit: 'dx-command-edit',
commandExpand: 'dx-command-expand',
commandSelect: 'dx-command-select',
commandAdaptive: 'dx-command-adaptive',
hiddenColumn: 'hidden-column',
commandLink: 'dx-link',
editCell: 'dx-editor-cell',
focused: 'dx-focused',
Expand Down Expand Up @@ -94,7 +96,7 @@ class Headers extends DxElement {
}

getHeaderRow(index: number): HeaderRow {
return new HeaderRow(this.element.find(`.${CLASS.headerRow}:nth-child(${++index})`));
return new HeaderRow(this.element.find(`.${CLASS.headerRow}:nth-child(${++index})`), this.widgetName);
}

getFilterRow(): FilterRow {
Expand Down Expand Up @@ -127,8 +129,11 @@ class FilterCell extends DxElement {
}

class HeaderCell extends DxElement {
constructor(headerRow: Selector, index: number) {
isHidden: Promise<boolean>;

constructor(headerRow: Selector, index: number, widgetName: string) {
super(headerRow.find(`td:nth-child(${++index})`));
this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn));
}

getFilterIcon(): Selector {
Expand All @@ -137,16 +142,19 @@ class HeaderCell extends DxElement {
}

class HeaderRow extends DxElement {
constructor(element: Selector) {
widgetName: string;

constructor(element: Selector, widgetName: string) {
super(element);
this.widgetName = widgetName;
}

getHeaderCell(index: number): HeaderCell {
return new HeaderCell(this.element, index);
return new HeaderCell(this.element, index, this.widgetName);
}

getCommandCell(index: number): CommandCell {
return new CommandCell(this.element, index);
return new CommandCell(this.element, index, this.widgetName);
}
}

Expand All @@ -158,15 +166,17 @@ class DataCell extends DxElement {
isInvalid: Promise<boolean>;
isModified: Promise<boolean>;
hasInvalidMessage: Promise<boolean>;
isHidden: Promise<boolean>;

constructor(dataRow: Selector, index: number) {
constructor(dataRow: Selector, index: number, widgetName: string) {
super(dataRow.find(`td:nth-child(${++index})`));
this.isEditCell = this.element.hasClass(CLASS.editCell);
this.isFocused = this.element.hasClass(CLASS.focused);
this.isValidationPending = this.element.find(`div.${CLASS.pendingIndicator}`).exists;
this.isInvalid = this.element.hasClass(CLASS.invalidCell);
this.isModified = this.element.hasClass(CLASS.cellModified);
this.hasInvalidMessage = this.element.find(`.${CLASS.invalidOverlayMessage} .${CLASS.popupContent}`).exists;
this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn));
}

getEditor(): DxElement {
Expand All @@ -176,11 +186,14 @@ class DataCell extends DxElement {

class CommandCell extends DxElement {
isFocused: Promise<boolean>;
isHidden: Promise<boolean>;

constructor(dataRow: Selector, index: number) {
constructor(dataRow: Selector, index: number, widgetName: string) {
const childrenSelector = `td:nth-child(${++index})`;
super(dataRow.find(`${childrenSelector}.${CLASS.commandEdit}, ${childrenSelector}.${CLASS.commandSelect}, ${childrenSelector}.${CLASS.commandExpand}`));
const commandSelector = `${childrenSelector}.${CLASS.commandEdit}, ${childrenSelector}.${CLASS.commandSelect}, ${childrenSelector}.${CLASS.commandExpand}, ${childrenSelector}.${CLASS.commandAdaptive}`;
super(dataRow.find(commandSelector));
this.isFocused = this.element.hasClass(CLASS.focused);
this.isHidden = this.element.hasClass(addWidgetPrefix(widgetName, CLASS.hiddenColumn));
}

getButton(index: number) {
Expand All @@ -193,14 +206,16 @@ class CommandCell extends DxElement {
}

class DataRow extends DxElement {
widgetName: string;
isRemoved: Promise<boolean>;
isFocusedRow: Promise<boolean>;
isSelected: Promise<boolean>;
isInserted: Promise<boolean>;
isEdited: Promise<boolean>;

constructor(element: Selector) {
constructor(element: Selector, widgetName: string) {
super(element);
this.widgetName = widgetName;
this.isRemoved = this.element.hasClass(CLASS.rowRemoved);
this.isFocusedRow = this.element.hasClass(CLASS.focusedRow);
this.isSelected = this.element.hasClass(CLASS.selection);
Expand All @@ -209,11 +224,11 @@ class DataRow extends DxElement {
}

getDataCell(index: number): DataCell {
return new DataCell(this.element, index);
return new DataCell(this.element, index, this.widgetName);
}

getCommandCell(index: number): CommandCell {
return new CommandCell(this.element, index);
return new CommandCell(this.element, index, this.widgetName);
}

getSelectCheckBox(): Selector {
Expand All @@ -236,7 +251,7 @@ class GroupRow extends DxElement {
}

getCell(index: number): DataCell {
return new DataCell(this.element, index);
return new DataCell(this.element, index, this.widgetName);
}
}

Expand Down Expand Up @@ -337,7 +352,7 @@ export default class DataGrid extends Widget {
}

getDataRow(index: number): DataRow {
return new DataRow(this.element.find(`.${CLASS.dataRow}:nth-child(${++index})`));
return new DataRow(this.element.find(`.${CLASS.dataRow}:nth-child(${++index})`), this.name);
}

getDataCell(rowIndex: number, columnIndex: number): DataCell {
Expand Down
82 changes: 82 additions & 0 deletions testing/functional/tests/dataGrid/keyboardNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,88 @@ test('Detail - The first command cell should be focused using Tab (T884646)', as
});
});

test('Adaptive - Hidden cells should not be focused using Tab (T887014)', async t => {
const dataGrid = new DataGrid('#container');
const headerRow = dataGrid.getHeaders().getHeaderRow(0);
const dataRow = dataGrid.getDataRow(0);

//header row
await t
.pressKey('tab')
.expect(headerRow.getHeaderCell(1).element.focused).ok()
.expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok()
.expect(headerRow.getHeaderCell(2).isHidden).ok()
.expect(headerRow.getHeaderCell(2).element.hasAttribute('tabindex')).notOk('the third header cell does not have tabindex')

.pressKey('tab')
.expect(headerRow.getHeaderCell(3).element.focused).ok()
.expect(headerRow.getHeaderCell(3).element.hasAttribute('tabindex')).ok();

//data row
await t
.pressKey('tab')
.expect(dataRow.getCommandCell(0).isFocused).ok()
.expect(dataRow.getCommandCell(0).element.focused).ok()

.pressKey('tab')
.expect(dataRow.getDataCell(1).isFocused).ok()
.expect(dataRow.getDataCell(1).element.focused).ok()
.expect(dataRow.getDataCell(2).isHidden).ok()
.expect(dataRow.getDataCell(2).element.hasAttribute('tabindex')).notOk('the third data cell does not have tabindex')

.pressKey('tab')
.expect(dataRow.getDataCell(3).isFocused).ok()
.expect(dataRow.getDataCell(3).element.focused).ok()

.pressKey('shift+tab')
.expect(dataRow.getDataCell(2).isHidden).ok()
.expect(dataRow.getDataCell(2).element.hasAttribute('tabindex')).notOk('the third data cell does not have tabindex')
.expect(dataRow.getDataCell(1).isFocused).ok()
.expect(dataRow.getDataCell(1).element.focused).ok()

.pressKey('shift+tab')
.expect(dataRow.getCommandCell(0).isFocused).ok()
.expect(dataRow.getCommandCell(0).element.focused).ok();

//header row
await t
.pressKey('shift+tab')
.expect(headerRow.getHeaderCell(3).element.focused).ok()
.expect(headerRow.getHeaderCell(3).element.hasAttribute('tabindex')).ok()

.pressKey('shift+tab')
.expect(headerRow.getHeaderCell(2).isHidden).ok()
.expect(headerRow.getHeaderCell(2).element.hasAttribute('tabindex')).notOk('the third header cell does not have tabindex')
.expect(headerRow.getHeaderCell(1).element.focused).ok()
.expect(headerRow.getHeaderCell(1).element.hasAttribute('tabindex')).ok()

// focus BODY
await t
.pressKey('shift+tab')
.expect(Selector('BODY').focused).ok();

}).before(async () => {
await createWidget('dxDataGrid', {
keyExpr: 'name',
dataSource: [
{ name: 'Alex', phone: '5555555555', room: 1 }
],
width: 150,
columnHidingEnabled: true,
columns: [
{
type: 'adaptive'
},
'name',
{
dataField: 'phone',
hidingPriority: 0
},
'room'
]
});
});

test('Select views by Ctrl+Up, Ctrl+Down keys', async t => {
const dataGrid = new DataGrid('#container');
const headers = dataGrid.getHeaders();
Expand Down

0 comments on commit 87a3040

Please sign in to comment.