devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
1,076 lines (906 loc) • 42.1 kB
JavaScript
"use strict";
var $ = require("../../core/renderer"),
domAdapter = require("../../core/dom_adapter"),
eventsEngine = require("../../events/core/events_engine"),
core = require("./ui.grid_core.modules"),
isDefined = require("../../core/utils/type").isDefined,
inArray = require("../../core/utils/array").inArray,
focused = require("../widget/selectors").focused,
each = require("../../core/utils/iterator").each,
KeyboardProcessor = require("../widget/ui.keyboard_processor"),
eventUtils = require("../../events/utils"),
pointerEvents = require("../../events/pointer");
var ROWS_VIEW_CLASS = "rowsview",
EDIT_FORM_CLASS = "edit-form",
GROUP_FOOTER_CLASS = "group-footer",
ROW_CLASS = "dx-row",
DATA_ROW_CLASS = "dx-data-row",
GROUP_ROW_CLASS = "dx-group-row",
EDIT_FORM_ITEM_CLASS = "edit-form-item",
MASTER_DETAIL_ROW_CLASS = "dx-master-detail-row",
FREESPACE_ROW_CLASS = "dx-freespace-row",
MASTER_DETAIL_CELL_CLASS = "dx-master-detail-cell",
DROPDOWN_EDITOR_OVERLAY_CLASS = "dx-dropdowneditor-overlay",
COMMAND_EXPAND_CLASS = "dx-command-expand",
CELL_FOCUS_DISABLED_CLASS = "dx-cell-focus-disabled",
INTERACTIVE_ELEMENTS_SELECTOR = "input:not([type='hidden']), textarea, a, [tabindex]",
VIEWS = ["rowsView"],
EDIT_MODE_ROW = "row",
EDIT_MODE_FORM = "form",
EDIT_MODE_BATCH = "batch",
EDIT_MODE_CELL = "cell";
function isGroupRow($row) {
return $row && $row.hasClass(GROUP_ROW_CLASS);
}
function isDetailRow($row) {
return $row && $row.hasClass(MASTER_DETAIL_ROW_CLASS);
}
function isFreeSpaceRow($row) {
return $row && $row.hasClass(FREESPACE_ROW_CLASS);
}
function isCellElement($element) {
return $element.length && $element[0].tagName === "TD";
}
var KeyboardNavigationController = core.ViewController.inherit({
_isRowEditMode: function _isRowEditMode() {
var editMode = this._editingController.getEditMode();
return editMode === EDIT_MODE_ROW || editMode === EDIT_MODE_FORM;
},
_isCellEditMode: function _isCellEditMode() {
var editMode = this._editingController.getEditMode();
return editMode === EDIT_MODE_CELL || editMode === EDIT_MODE_BATCH;
},
_focusView: function _focusView(view, viewIndex) {
this._focusedViews.viewIndex = viewIndex;
this._focusedView = view;
},
_getInteractiveElement: function _getInteractiveElement($cell, isLast) {
var $focusedElement = $cell.find(INTERACTIVE_ELEMENTS_SELECTOR).filter(":visible");
return isLast ? $focusedElement.last() : $focusedElement.first();
},
_focusInteractiveElement: function _focusInteractiveElement($cell, isLast) {
if (!$cell) return;
var $focusedElement = this._getInteractiveElement($cell, isLast);
///#DEBUG
this._testInteractiveElement = $focusedElement;
///#ENDDEBUG
eventsEngine.trigger($focusedElement, "focus");
},
_updateFocus: function _updateFocus() {
var that = this,
$cell = that._getFocusedCell();
if ($cell && !(that._isMasterDetailCell($cell) && !that._isRowEditMode())) {
if (that._hasSkipRow($cell.parent())) {
$cell = that._getNextCell(this._focusedCellPosition && this._focusedCellPosition.rowIndex > 0 ? "upArrow" : "downArrow");
}
if ($cell && $cell.length > 0) {
setTimeout(function () {
if ($cell.is("td") || $cell.hasClass(that.addWidgetPrefix(EDIT_FORM_ITEM_CLASS))) {
if (that.getController("editorFactory").focus()) {
that._focus($cell);
} else if (that._isHiddenFocus) {
that._focus($cell, true);
}
if (that._editingController.isEditing()) {
that._focusInteractiveElement.bind(that)($cell);
}
} else {
eventsEngine.trigger($cell, "focus");
}
});
}
}
},
_applyTabIndexToElement: function _applyTabIndexToElement($element) {
var tabIndex = this.option("tabIndex");
$element.attr("tabIndex", isDefined(tabIndex) ? tabIndex : 0);
},
_clickHandler: function _clickHandler(e) {
var event = e.event,
$target = $(event.currentTarget),
$grid = $(event.target).closest("." + this.getWidgetContainerClass()).parent(),
data = event.data;
if ($grid.is(this.component.$element()) && this._isCellValid($target)) {
$target = this._isInsideEditForm($target) ? $(event.target) : $target;
this._focusView(data.view, data.viewIndex);
this._updateFocusedCellPosition($target);
if (!this._editingController.isEditing() && !this._isCellEditMode() && !this._isMasterDetailCell($target)) {
this._focus($target, true);
}
} else if ($target.is("td")) {
this._resetFocusedCell();
}
},
_initFocusedViews: function _initFocusedViews() {
var that = this,
clickAction = that.createAction(that._clickHandler);
that._focusedViews = [];
each(VIEWS, function (key, viewName) {
var view = that.getView(viewName);
if (view && view.isVisible()) {
that._focusedViews.push(view);
}
});
each(that._focusedViews, function (index, view) {
if (view) {
view.renderCompleted.add(function () {
var $element = view.element();
eventsEngine.off($element, eventUtils.addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), clickAction);
eventsEngine.on($element, eventUtils.addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), "." + ROW_CLASS + " td, ." + ROW_CLASS, {
viewIndex: index,
view: view
}, clickAction);
that._initKeyDownProcessor(that, $element, that._keyDownHandler);
if (that._focusedView && that._focusedView.name === view.name && (that._isNeedFocus || that._isHiddenFocus)) {
that._updateFocus();
}
});
}
});
},
_initKeyDownProcessor: function _initKeyDownProcessor(context, element, handler) {
if (this._keyDownProcessor) {
this._keyDownProcessor.dispose();
this._keyDownProcessor = null;
}
this._keyDownProcessor = new KeyboardProcessor({
element: element,
context: context,
handler: handler
});
},
_getCell: function _getCell(cellPosition) {
if (this._focusedView && cellPosition) {
return this._focusedView.getCell({
rowIndex: cellPosition.rowIndex - this._dataController.getRowIndexOffset(),
columnIndex: cellPosition.columnIndex
});
}
},
_getFocusedCell: function _getFocusedCell() {
return this._getCell(this._focusedCellPosition);
},
_getRowIndex: function _getRowIndex($row) {
var that = this,
focusedView = that._focusedView,
rowIndex = -1;
if (focusedView) {
rowIndex = focusedView.getRowIndex($row);
}
if (rowIndex >= 0) {
rowIndex += that._dataController.getRowIndexOffset();
}
return rowIndex;
},
_updateFocusedCellPosition: function _updateFocusedCellPosition($cell, direction) {
var that = this,
rowIndex,
columnIndex,
$rowElement = $cell.closest("tr");
if ($rowElement.length > 0 && that._focusedView) {
rowIndex = $rowElement.length > 0 && that._getRowIndex($rowElement);
columnIndex = that._focusedView.getCellIndex($cell, rowIndex);
if (direction) {
columnIndex = direction === "previous" ? columnIndex - 1 : columnIndex + 1;
columnIndex = that._applyColumnIndexBoundaries(columnIndex);
}
this._focusedCellPosition = {
columnIndex: columnIndex,
rowIndex: rowIndex
};
}
},
_applyColumnIndexBoundaries: function _applyColumnIndexBoundaries(columnIndex) {
var visibleColumnsCount = this._getVisibleColumnCount();
if (columnIndex < 0) {
columnIndex = 0;
} else if (columnIndex >= visibleColumnsCount) {
columnIndex = visibleColumnsCount - 1;
}
return columnIndex;
},
_isCellValid: function _isCellValid($cell) {
if (isDefined($cell)) {
var rowsView = this.getView("rowsView"),
visibleColumns = this._columnsController.getVisibleColumns(),
visibleRowIndex = rowsView.getRowIndex($cell.parent()),
columnIndex = rowsView.getCellIndex($cell),
column = visibleColumns[columnIndex],
visibleColumnCount = this._getVisibleColumnCount(),
editingController = this._editingController,
editMode = editingController && editingController.getEditMode(),
isEditingCurrentRow = editingController && (editMode === EDIT_MODE_ROW ? editingController.isEditRow(visibleRowIndex) : editingController.isEditing()),
isMasterDetailRow = isDetailRow($cell.parent()),
isValidGroupSpaceColumn = function isValidGroupSpaceColumn() {
return !isMasterDetailRow && column && !isDefined(column.groupIndex) || parseInt($cell.attr("colspan")) > 1;
};
if (this._isMasterDetailCell($cell)) {
return true;
}
if (visibleColumnCount > columnIndex && isValidGroupSpaceColumn()) {
var isExpandColumn = column.command === "expand";
return column && !column.command && (!isEditingCurrentRow || column.allowEditing) || !isEditingCurrentRow && isExpandColumn;
}
}
},
_isCellByPositionValid: function _isCellByPositionValid(cellPosition) {
var $cell = this._getCell(cellPosition);
return this._isCellValid($cell);
},
_focus: function _focus($cell, disableFocus) {
var $row = $cell.parent();
if (isFreeSpaceRow($row)) {
return;
}
var $focusedCell = this._getFocusedCell(),
focusedView = this._focusedView,
$focusViewElement = focusedView && focusedView.element(),
$focusElement;
$focusedCell && $focusedCell.is("td") && $focusedCell.removeAttr("tabIndex");
this._isHiddenFocus = disableFocus;
if (isGroupRow($row)) {
$focusElement = $row;
if (focusedView) {
this._focusedCellPosition.rowIndex = this._getRowIndex($row);
}
} else if (isCellElement($cell)) {
$focusElement = $cell;
this._updateFocusedCellPosition($cell);
}
if ($focusElement) {
this._applyTabIndexToElement($focusElement);
eventsEngine.trigger($focusElement, "focus");
}
if (disableFocus) {
$focusViewElement && $focusViewElement.find("." + CELL_FOCUS_DISABLED_CLASS + "[tabIndex]").removeClass(CELL_FOCUS_DISABLED_CLASS).removeAttr("tabIndex");
$focusElement.addClass(CELL_FOCUS_DISABLED_CLASS);
} else {
$focusViewElement && $focusViewElement.find("." + CELL_FOCUS_DISABLED_CLASS + ":not(." + MASTER_DETAIL_CELL_CLASS + ")").removeClass(CELL_FOCUS_DISABLED_CLASS);
this.getController("editorFactory").focus($focusElement);
}
},
_hasSkipRow: function _hasSkipRow($row) {
var row = $row && $row.get(0);
return row && (row.style.display === "none" || $row.hasClass(this.addWidgetPrefix(GROUP_FOOTER_CLASS)) || isDetailRow($row) && !$row.hasClass(this.addWidgetPrefix(EDIT_FORM_CLASS)));
},
_enterKeyHandler: function _enterKeyHandler(eventArgs, isEditing) {
var $cell = this._getFocusedCell(),
editingOptions = this.option("editing"),
rowIndex = this._getFocusedRowIndex(),
$row = this._focusedView && this._focusedView.getRow(rowIndex);
if (this.option("grouping.allowCollapsing") && isGroupRow($row) || this.option("masterDetail.enabled") && $cell && $cell.hasClass(COMMAND_EXPAND_CLASS)) {
var key = this._dataController.getKeyByRowIndex(rowIndex),
item = this._dataController.items()[rowIndex];
if (key !== undefined && item && item.data && !item.data.isContinuation) {
this._dataController.changeRowExpand(key);
}
} else {
if (isEditing) {
$cell = this._getCellElementFromTarget(eventArgs.originalEvent.target);
this._updateFocusedCellPosition($cell);
if (this._isRowEditMode()) {
this._focusEditFormCell($cell);
setTimeout(this._editingController.saveEditData.bind(this._editingController));
} else {
var $target = $(eventArgs.originalEvent.target);
eventsEngine.trigger($target, "blur");
eventsEngine.trigger($target, "focus");
this._editingController.closeEditCell();
eventArgs.originalEvent.preventDefault();
}
} else {
var column = this._columnsController.getVisibleColumns()[this._focusedCellPosition.columnIndex];
if (editingOptions.allowUpdating && column && column.allowEditing) {
if (this._isRowEditMode()) {
this._editingController.editRow(rowIndex);
} else {
this._focusedCellPosition && this._editingController.editCell(rowIndex, this._focusedCellPosition.columnIndex);
}
}
}
}
},
_getFocusedRowIndex: function _getFocusedRowIndex() {
if (this._focusedCellPosition) {
return this._focusedCellPosition.rowIndex - this._dataController.getRowIndexOffset();
}
return null;
},
_leftRightKeysHandler: function _leftRightKeysHandler(eventArgs, isEditing) {
var rowIndex = this._getFocusedRowIndex(),
$row = this._focusedView && this._focusedView.getRow(rowIndex),
directionCode,
$cell;
if (!isEditing && $row && !isGroupRow($row) && !isDetailRow($row)) {
directionCode = this._getDirectionCodeByKey(eventArgs.key);
$cell = this._getNextCell(directionCode);
if ($cell && this._isCellValid($cell)) {
this._focus($cell);
}
eventArgs.originalEvent.preventDefault();
}
},
_getDirectionCodeByKey: function _getDirectionCodeByKey(key) {
var directionCode;
if (this.option("rtlEnabled")) {
directionCode = key === "leftArrow" ? "nextInRow" : "previousInRow";
} else {
directionCode = key === "leftArrow" ? "previousInRow" : "nextInRow";
}
return directionCode;
},
_upDownKeysHandler: function _upDownKeysHandler(eventArgs, isEditing) {
var rowIndex = this._getFocusedRowIndex(),
$row = this._focusedView && this._focusedView.getRow(rowIndex),
$cell;
if (!isEditing && $row && !isDetailRow($row)) {
$cell = this._getNextCell(eventArgs.key);
if ($cell && this._isCellValid($cell)) {
this._focus($cell);
}
eventArgs.originalEvent.preventDefault();
}
},
_isVirtualScrolling: function _isVirtualScrolling() {
var scrollingMode = this.option("scrolling.mode");
return scrollingMode === "virtual" || scrollingMode === "infinite";
},
_scrollBy: function _scrollBy(top) {
var that = this,
scrollable = this.getView("rowsView").getScrollable();
if (that._focusedCellPosition) {
var scrollHandler = function scrollHandler() {
scrollable.off(scrollHandler);
setTimeout(function () {
var columnIndex = that._focusedCellPosition.columnIndex;
var rowIndex = that.getView("rowsView").getTopVisibleItemIndex() + that._dataController.getRowIndexOffset();
that.getController("editorFactory").loseFocus();
var $rowsView = that.getView("rowsView").element();
that._applyTabIndexToElement($rowsView);
eventsEngine.trigger($rowsView, "focus");
that._focusedCellPosition.rowIndex = rowIndex;
that._focusedCellPosition.columnIndex = columnIndex;
});
};
scrollable.on("scroll", scrollHandler);
}
scrollable.scrollBy({ left: 0, top: top });
},
_pageUpDownKeyHandler: function _pageUpDownKeyHandler(eventArgs) {
var pageIndex = this._dataController.pageIndex(),
pageCount = this._dataController.pageCount(),
pagingEnabled = this.option("paging.enabled"),
isPageUp = eventArgs.key === "pageUp",
pageStep = isPageUp ? -1 : 1,
scrollable = this.getView("rowsView").getScrollable();
if (pagingEnabled && !this._isVirtualScrolling()) {
if ((isPageUp ? pageIndex > 0 : pageIndex < pageCount - 1) && !this._isVirtualScrolling()) {
this._dataController.pageIndex(pageIndex + pageStep);
eventArgs.originalEvent.preventDefault();
}
} else if (scrollable && scrollable._container().height() < scrollable.$content().height()) {
this._scrollBy(scrollable._container().height() * pageStep);
eventArgs.originalEvent.preventDefault();
}
},
_spaceKeyHandler: function _spaceKeyHandler(eventArgs, isEditing) {
var rowIndex = this._getFocusedRowIndex(),
$target = $(eventArgs.originalEvent && eventArgs.originalEvent.target);
if (this.option("selection") && this.option("selection").mode !== "none" && !isEditing && ($target.parent().hasClass(DATA_ROW_CLASS) || $target.hasClass(this.addWidgetPrefix(ROWS_VIEW_CLASS)))) {
this._selectionController.changeItemSelection(rowIndex, {
shift: eventArgs.shift,
control: eventArgs.ctrl
});
eventArgs.originalEvent.preventDefault();
}
},
_ctrlAKeyHandler: function _ctrlAKeyHandler(eventArgs, isEditing) {
if (!isEditing && eventArgs.ctrl && !eventArgs.alt && this.option("selection.mode") === "multiple" && this.option("selection.allowSelectAll")) {
this._selectionController.selectAll();
eventArgs.originalEvent.preventDefault();
}
},
_isInsideEditForm: function _isInsideEditForm(element) {
return $(element).closest("." + this.addWidgetPrefix(EDIT_FORM_CLASS)).length > 0;
},
_isMasterDetailCell: function _isMasterDetailCell(element) {
var $masterDetailCell = $(element).closest("." + MASTER_DETAIL_CELL_CLASS),
$masterDetailGrid = $masterDetailCell.closest("." + this.getWidgetContainerClass()).parent();
return $masterDetailCell.length && $masterDetailGrid.is(this.component.$element());
},
_processNextCellInMasterDetail: function _processNextCellInMasterDetail($nextCell) {
if (!this._isInsideEditForm($nextCell) && $nextCell) {
this._applyTabIndexToElement($nextCell);
}
},
_handleTabKeyOnMasterDetailCell: function _handleTabKeyOnMasterDetailCell(target, direction) {
if (this._isMasterDetailCell(target)) {
this._updateFocusedCellPosition($(target), direction);
var $nextCell = this._getNextCell(direction, "row");
this._processNextCellInMasterDetail($nextCell);
return true;
}
return false;
},
_tabKeyHandler: function _tabKeyHandler(eventArgs, isEditing) {
var editingOptions = this.option("editing"),
direction = eventArgs.shift ? "previous" : "next",
isOriginalHandlerRequired = !eventArgs.shift && this._isLastValidCell(this._focusedCellPosition) || eventArgs.shift && this._isFirstValidCell(this._focusedCellPosition),
eventTarget = eventArgs.originalEvent.target,
$cell;
if (this._handleTabKeyOnMasterDetailCell(eventTarget, direction)) {
return;
}
if (editingOptions && eventTarget && !isOriginalHandlerRequired) {
if ($(eventTarget).hasClass(this.addWidgetPrefix(ROWS_VIEW_CLASS))) {
this._resetFocusedCell();
}
if (isEditing) {
var column, row, isEditingAllowed;
this._updateFocusedCellPosition(this._getCellElementFromTarget(eventTarget));
$cell = this._getNextCell(direction);
if (!$cell || this._handleTabKeyOnMasterDetailCell($cell, direction)) {
return;
}
column = this._columnsController.getVisibleColumns()[this.getView("rowsView").getCellIndex($cell)];
row = this._dataController.items()[this._getRowIndex($cell && $cell.parent())];
isEditingAllowed = (editingOptions.allowUpdating || row && row.inserted) && column.allowEditing;
if (!isEditingAllowed) {
this._editingController.closeEditCell();
}
if (this._focusCell($cell)) {
if (!this._isRowEditMode() && isEditingAllowed) {
this._editingController.editCell(this._getFocusedRowIndex(), this._focusedCellPosition.columnIndex);
} else {
this._focusInteractiveElement($cell, eventArgs.shift);
}
}
} else {
$cell = this._getCellElementFromTarget(eventTarget);
var $lastInteractiveElement = this._getInteractiveElement($cell, !eventArgs.shift);
if ($lastInteractiveElement.length && eventTarget !== $lastInteractiveElement.get(0)) {
isOriginalHandlerRequired = true;
} else {
if (this._focusedCellPosition.rowIndex === undefined && $(eventTarget).hasClass(ROW_CLASS)) {
this._updateFocusedCellPosition($(eventTarget).children().first());
}
$cell = this._getNextCell(direction, this._getElementType(eventTarget));
this._focusCell($cell);
this._focusInteractiveElement($cell, eventArgs.shift);
}
}
}
if (isOriginalHandlerRequired) {
this.getController("editorFactory").loseFocus();
if (this._editingController.isEditing() && !this._isRowEditMode()) {
this._resetFocusedCell();
this._editingController.closeEditCell();
}
} else {
eventArgs.originalEvent.preventDefault();
}
},
_focusCell: function _focusCell($cell) {
if (this._isCellValid($cell)) {
this._focus($cell);
return true;
}
},
_getElementType: function _getElementType(target) {
return $(target).is("tr") ? "row" : "cell";
},
_focusEditFormCell: function _focusEditFormCell($cell) {
if ($cell.hasClass(MASTER_DETAIL_CELL_CLASS)) {
this.getController("editorFactory").focus($cell, true);
}
},
_escapeKeyHandler: function _escapeKeyHandler(eventArgs, isEditing) {
if (isEditing) {
var $cell = this._getCellElementFromTarget(eventArgs.originalEvent.target);
this._updateFocusedCellPosition($cell);
if (!this._isRowEditMode()) {
if (this._editingController.getEditMode() === "cell") {
this._editingController.cancelEditData();
} else {
this._editingController.closeEditCell();
}
} else {
this._focusEditFormCell($cell);
this._editingController.cancelEditData();
}
eventArgs.originalEvent.preventDefault();
}
},
_ctrlFKeyHandler: function _ctrlFKeyHandler(eventArgs) {
if (eventArgs.ctrl && this.option("searchPanel") && this.option("searchPanel").visible) {
///#DEBUG
this._testHeaderPanelFocused = true;
///#ENDDEBUG
this._headerPanel.focus();
eventArgs.originalEvent.preventDefault();
}
},
_keyDownHandler: function _keyDownHandler(e) {
var isEditing = this._editingController.isEditing(),
needStopPropagation = true,
args = {
handled: false,
event: e.originalEvent
};
this.executeAction("onKeyDown", args);
if (e.originalEvent.isDefaultPrevented()) {
return;
}
this._isNeedFocus = true;
this._isNeedScroll = true;
this._updateFocusedCellPosition(this._getCellElementFromTarget(args.event.target));
if (!args.handled) {
switch (e.key) {
case "leftArrow":
case "rightArrow":
this._leftRightKeysHandler(e, isEditing);
break;
case "upArrow":
case "downArrow":
this._upDownKeysHandler(e, isEditing);
break;
case "pageUp":
case "pageDown":
this._pageUpDownKeyHandler(e);
break;
case "space":
this._spaceKeyHandler(e, isEditing);
break;
case "A":
this._ctrlAKeyHandler(e, isEditing);
break;
case "tab":
this._tabKeyHandler(e, isEditing);
break;
case "enter":
this._enterKeyHandler(e, isEditing);
break;
case "escape":
this._escapeKeyHandler(e, isEditing);
break;
case "F":
this._ctrlFKeyHandler(e);
break;
default:
this._isNeedFocus = false;
this._isNeedScroll = false;
needStopPropagation = false;
break;
}
if (needStopPropagation) {
e.originalEvent.stopPropagation();
}
}
},
_isLastRow: function _isLastRow(rowIndex) {
if (this._isVirtualScrolling()) {
return rowIndex >= this._dataController.totalItemsCount() - 1;
}
return rowIndex === this.getController("data").items().length - 1;
},
_getNextCell: function _getNextCell(keyCode, elementType, cellPosition) {
var focusedCellPosition = cellPosition || this._focusedCellPosition,
includeCommandCells = inArray(keyCode, ["next", "previous"]) > -1,
rowIndex,
newFocusedCellPosition,
isLastCellOnDirection = keyCode === "previous" ? this._isFirstValidCell(focusedCellPosition) : this._isLastValidCell(focusedCellPosition),
$cell,
$row;
if (this._focusedView && focusedCellPosition) {
newFocusedCellPosition = this._getNewPositionByCode(focusedCellPosition, elementType, keyCode);
$cell = this._getCell(newFocusedCellPosition);
if (!this._isCellValid($cell) && this._isCellInRow(newFocusedCellPosition, includeCommandCells) && !isLastCellOnDirection) {
$cell = this._getNextCell(keyCode, "cell", newFocusedCellPosition);
}
$row = $cell && $cell.parent();
if (this._hasSkipRow($row)) {
rowIndex = this._getRowIndex($row);
if (!this._isLastRow(rowIndex)) {
$cell = this._getNextCell(keyCode, "row", { columnIndex: focusedCellPosition.columnIndex, rowIndex: rowIndex });
} else {
return null;
}
}
return $cell;
}
return null;
},
_getNewPositionByCode: function _getNewPositionByCode(cellPosition, elementType, code) {
var columnIndex = cellPosition.columnIndex,
rowIndex = cellPosition.rowIndex,
visibleColumnsCount;
if (cellPosition.rowIndex === undefined && code === "next") {
return { columnIndex: 0, rowIndex: 0 };
}
switch (code) {
case "nextInRow":
case "next":
visibleColumnsCount = this._getVisibleColumnCount();
if (columnIndex < visibleColumnsCount - 1 && !this._isLastValidCell({ columnIndex: columnIndex, rowIndex: rowIndex }) && elementType !== "row") {
columnIndex++;
} else if (!this._isLastRow(rowIndex) && code === "next") {
columnIndex = 0;
rowIndex++;
}
break;
case "previousInRow":
case "previous":
if (columnIndex > 0 && !this._isFirstValidCell({ columnIndex: columnIndex, rowIndex: rowIndex }) && elementType !== "row") {
columnIndex--;
} else if (rowIndex > 0 && code === "previous") {
rowIndex--;
visibleColumnsCount = this._getVisibleColumnCount();
columnIndex = visibleColumnsCount - 1;
}
break;
case "upArrow":
rowIndex = rowIndex > 0 ? rowIndex - 1 : rowIndex;
break;
case "downArrow":
rowIndex = !this._isLastRow(rowIndex) ? rowIndex + 1 : rowIndex;
break;
}
return { columnIndex: columnIndex, rowIndex: rowIndex };
},
_isFirstValidCell: function _isFirstValidCell(cellPosition) {
var isFirstValidCell = false;
if (cellPosition.rowIndex === 0 && cellPosition.columnIndex >= 0) {
isFirstValidCell = isFirstValidCell || !this._haveValidCellBeforePosition(cellPosition);
}
return isFirstValidCell;
},
_haveValidCellBeforePosition: function _haveValidCellBeforePosition(cellPosition) {
var columnIndex = cellPosition.columnIndex,
hasValidCells = false;
while (columnIndex > 0 && !hasValidCells) {
var checkingPosition = { columnIndex: --columnIndex, rowIndex: cellPosition.rowIndex };
hasValidCells = this._isCellByPositionValid(checkingPosition);
}
return hasValidCells;
},
_isLastValidCell: function _isLastValidCell(cellPosition) {
var checkingPosition = { columnIndex: cellPosition.columnIndex + 1, rowIndex: cellPosition.rowIndex },
visibleColumnsCount = this._getVisibleColumnCount(),
isCheckingCellValid = this._isCellByPositionValid(checkingPosition);
if (!this._isLastRow(cellPosition.rowIndex)) {
return false;
}
if (cellPosition.columnIndex === visibleColumnsCount - 1) {
return true;
}
if (isCheckingCellValid) {
return false;
}
return this._isLastValidCell(checkingPosition);
},
_getVisibleColumnCount: function _getVisibleColumnCount() {
return this.getController("columns").getVisibleColumns().length;
},
_isCellInRow: function _isCellInRow(cellPosition, includeCommandCells) {
var columnIndex = cellPosition.columnIndex,
visibleColumnsCount = this._getVisibleColumnCount();
return includeCommandCells ? columnIndex >= 0 && columnIndex <= visibleColumnsCount - 1 : columnIndex > 0 && columnIndex < visibleColumnsCount - 1;
},
_resetFocusedCell: function _resetFocusedCell() {
var that = this,
$cell = that._getFocusedCell();
$cell && $cell.removeAttr("tabIndex");
that._focusedView && that._focusedView.renderFocusState && that._focusedView.renderFocusState();
that._isNeedFocus = false;
that._isNeedScroll = false;
that._focusedCellPosition = {};
},
_getCellElementFromTarget: function _getCellElementFromTarget(target) {
return $(target).closest("." + ROW_CLASS + "> td");
},
init: function init() {
var that = this;
if (that.option("useKeyboard")) {
that._dataController = that.getController("data");
that._selectionController = that.getController("selection");
that._editingController = that.getController("editing");
that._headerPanel = that.getView("headerPanel");
that._columnsController = that.getController("columns");
that.getController("editorFactory").focused.add(function ($element) {
that.setupFocusedView();
if (that._isNeedScroll) {
if ($element.is(":visible") && that._focusedView && that._focusedView.getScrollable) {
that._scrollToElement($element);
that._isNeedScroll = false;
}
}
});
that._focusedCellPosition = {};
that._initFocusedViews();
that._documentClickHandler = that.createAction(function (e) {
var $target = $(e.event.target);
if (!$target.closest("." + that.addWidgetPrefix(ROWS_VIEW_CLASS)).length && !$target.closest("." + DROPDOWN_EDITOR_OVERLAY_CLASS).length) {
that._resetFocusedCell();
}
});
that.createAction("onKeyDown");
eventsEngine.on(domAdapter.getDocument(), eventUtils.addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), that._documentClickHandler);
}
},
_scrollToElement: function _scrollToElement($element, offset) {
var scrollable = this._focusedView.getScrollable();
scrollable && scrollable.update();
scrollable && scrollable.scrollToElement($element, offset);
},
/**
* @name GridBaseMethods.focus
* @publicName focus(element)
* @param1 element:Node|jQuery
*/
focus: function focus(element) {
var $element = $(element);
var focusView = this._getFocusedViewByElement($element);
if (focusView) {
this._focusView(focusView.view, focusView.viewIndex);
this._isNeedFocus = true;
this._isNeedScroll = true;
this._focus($element);
this._focusInteractiveElement($element);
}
},
getFocusedView: function getFocusedView() {
return this._focusedView;
},
_getFocusedViewByElement: function _getFocusedViewByElement($element) {
var condition = function condition(view) {
return $element.closest(view._$element).length;
};
return this._getFocusedViewByCondition(condition);
},
_getFocusedViewByCondition: function _getFocusedViewByCondition(conditionFunction) {
var focusView;
each(this._focusedViews, function (index, view) {
if (conditionFunction(view)) {
focusView = {
viewIndex: index,
view: view
};
return false;
}
});
return focusView;
},
focusViewByName: function focusViewByName(viewName) {
var view = this._getFocusedViewByName(viewName);
this._focusView(view.view, view.viewIndex);
},
setupFocusedView: function setupFocusedView() {
if (this.option("useKeyboard") && !isDefined(this._focusedView)) {
this.focusViewByName("rowsView");
}
},
_getFocusedViewByName: function _getFocusedViewByName(viewName) {
var condition = function condition(view) {
return view.name === viewName;
};
return this._getFocusedViewByCondition(condition);
},
optionChanged: function optionChanged(args) {
var that = this;
switch (args.name) {
case "useKeyboard":
// TODO implement
args.handled = true;
break;
default:
that.callBase(args);
}
},
dispose: function dispose() {
this.callBase();
this._focusedView = null;
this._focusedViews = null;
this._keyDownProcessor && this._keyDownProcessor.dispose();
eventsEngine.off(domAdapter.getDocument(), eventUtils.addNamespace(pointerEvents.down, "dxDataGridKeyboardNavigation"), this._documentClickHandler);
}
});
module.exports = {
defaultOptions: function defaultOptions() {
return {
useKeyboard: true
/**
* @name GridBaseOptions.onKeyDown
* @publicName onKeyDown
* @type function(e)
* @type_function_param1 e:object
* @type_function_param1_field3 jQueryEvent:jQuery.Event:deprecated(event)
* @type_function_param1_field4 event:event
* @type_function_param1_field5 handled:boolean
* @extends Action
* @action
*/
};
},
controllers: {
keyboardNavigation: KeyboardNavigationController
},
extenders: {
views: {
rowsView: {
renderFocusState: function renderFocusState() {
var that = this,
cellElements = that.getCellElements(0),
keyboardNavigation = that.getController("keyboardNavigation"),
tabIndex = that.option("tabIndex"),
oldFocusedView = keyboardNavigation._focusedView,
$row,
$cell,
$element = that.element();
if ($element && !focused($element)) {
$element.attr("tabIndex", null);
}
if (that.option("useKeyboard") && cellElements) {
$row = cellElements.eq(0).parent();
if (isGroupRow($row)) {
$row.attr("tabIndex", tabIndex);
} else {
keyboardNavigation._focusedView = that;
for (var i = 0; i < cellElements.length; i++) {
$cell = cellElements.eq(i);
if (keyboardNavigation._isCellValid($cell)) {
if (isCellElement($cell)) {
$cell.attr("tabIndex", tabIndex);
}
break;
}
}
keyboardNavigation._focusedView = oldFocusedView;
}
}
},
renderDelayedTemplates: function renderDelayedTemplates() {
this.callBase.apply(this, arguments);
this.renderFocusState();
},
_renderCore: function _renderCore(change) {
this.callBase(change);
this.renderFocusState();
}
}
},
controllers: {
editing: {
editCell: function editCell(rowIndex, columnIndex) {
var isCellEditing = this.callBase(rowIndex, columnIndex),
keyboardNavigationController = this.getController("keyboardNavigation");
if (isCellEditing) {
keyboardNavigationController.setupFocusedView();
}
return isCellEditing;
},
editRow: function editRow(rowIndex) {
if (this.option("editing.mode") === EDIT_MODE_FORM) {
this._keyboardNavigationController._resetFocusedCell();
}
this.callBase(rowIndex);
},
addRow: function addRow(parentKey) {
this.getController("keyboardNavigation").setupFocusedView();
this.callBase.apply(this, arguments);
},
getFocusedCellInRow: function getFocusedCellInRow(rowIndex) {
var keyboardNavigationController = this.getController("keyboardNavigation"),
$cell = this.callBase(rowIndex);
if (this.option("useKeyboard") && keyboardNavigationController._focusedCellPosition.rowIndex === rowIndex) {
$cell = keyboardNavigationController._getFocusedCell() || $cell;
}
return $cell;
},
init: function init() {
this.callBase();
this._keyboardNavigationController = this.getController("keyboardNavigation");
}
}
}
}
};