UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

920 lines (915 loc) • 41.4 kB
/** * DevExtreme (ui/grid_core/ui.grid_core.keyboard_navigation.js) * Version: 18.1.3 * Build date: Tue May 15 2018 * * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "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 && "TD" === $element[0].tagName } var KeyboardNavigationController = core.ViewController.inherit({ _isRowEditMode: function() { var editMode = this._editingController.getEditMode(); return editMode === EDIT_MODE_ROW || editMode === EDIT_MODE_FORM }, _isCellEditMode: function() { var editMode = this._editingController.getEditMode(); return editMode === EDIT_MODE_CELL || editMode === EDIT_MODE_BATCH }, _focusView: function(view, viewIndex) { this._focusedViews.viewIndex = viewIndex; this._focusedView = view }, _getInteractiveElement: function($cell, isLast) { var $focusedElement = $cell.find(INTERACTIVE_ELEMENTS_SELECTOR).filter(":visible"); return isLast ? $focusedElement.last() : $focusedElement.first() }, _focusInteractiveElement: function($cell, isLast) { if (!$cell) { return } var $focusedElement = this._getInteractiveElement($cell, isLast); eventsEngine.trigger($focusedElement, "focus") }, _updateFocus: function() { 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($element) { var tabIndex = this.option("tabIndex"); $element.attr("tabIndex", isDefined(tabIndex) ? tabIndex : 0) }, _clickHandler: function(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() { 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(context, element, handler) { if (this._keyDownProcessor) { this._keyDownProcessor.dispose(); this._keyDownProcessor = null } this._keyDownProcessor = new KeyboardProcessor({ element: element, context: context, handler: handler }) }, _getCell: function(cellPosition) { if (this._focusedView && cellPosition) { return this._focusedView.getCell({ rowIndex: cellPosition.rowIndex - this._dataController.getRowIndexOffset(), columnIndex: cellPosition.columnIndex }) } }, _getFocusedCell: function() { return this._getCell(this._focusedCellPosition) }, _getRowIndex: function($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($cell, direction) { var rowIndex, columnIndex, that = this, $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 = "previous" === direction ? columnIndex - 1 : columnIndex + 1; columnIndex = that._applyColumnIndexBoundaries(columnIndex) } this._focusedCellPosition = { columnIndex: columnIndex, rowIndex: rowIndex } } }, _applyColumnIndexBoundaries: function(columnIndex) { var visibleColumnsCount = this._getVisibleColumnCount(); if (columnIndex < 0) { columnIndex = 0 } else { if (columnIndex >= visibleColumnsCount) { columnIndex = visibleColumnsCount - 1 } } return columnIndex }, _isCellValid: function($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() { return !isMasterDetailRow && column && !isDefined(column.groupIndex) || parseInt($cell.attr("colspan")) > 1 }; if (this._isMasterDetailCell($cell)) { return true } if (visibleColumnCount > columnIndex && isValidGroupSpaceColumn()) { var isExpandColumn = "expand" === column.command; return column && !column.command && (!isEditingCurrentRow || column.allowEditing) || !isEditingCurrentRow && isExpandColumn } } }, _isCellByPositionValid: function(cellPosition) { var $cell = this._getCell(cellPosition); return this._isCellValid($cell) }, _focus: function($cell, disableFocus) { var $row = $cell.parent(); if (isFreeSpaceRow($row)) { return } var $focusElement, $focusedCell = this._getFocusedCell(), focusedView = this._focusedView, $focusViewElement = focusedView && focusedView.element(); $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($row) { var row = $row && $row.get(0); return row && ("none" === row.style.display || $row.hasClass(this.addWidgetPrefix(GROUP_FOOTER_CLASS)) || isDetailRow($row) && !$row.hasClass(this.addWidgetPrefix(EDIT_FORM_CLASS))) }, _enterKeyHandler: function(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 (void 0 !== key && 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() { if (this._focusedCellPosition) { return this._focusedCellPosition.rowIndex - this._dataController.getRowIndexOffset() } return null }, _leftRightKeysHandler: function(eventArgs, isEditing) { var directionCode, $cell, rowIndex = this._getFocusedRowIndex(), $row = this._focusedView && this._focusedView.getRow(rowIndex); 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(key) { var directionCode; if (this.option("rtlEnabled")) { directionCode = "leftArrow" === key ? "nextInRow" : "previousInRow" } else { directionCode = "leftArrow" === key ? "previousInRow" : "nextInRow" } return directionCode }, _upDownKeysHandler: function(eventArgs, isEditing) { var $cell, rowIndex = this._getFocusedRowIndex(), $row = this._focusedView && this._focusedView.getRow(rowIndex); if (!isEditing && $row && !isDetailRow($row)) { $cell = this._getNextCell(eventArgs.key); if ($cell && this._isCellValid($cell)) { this._focus($cell) } eventArgs.originalEvent.preventDefault() } }, _isVirtualScrolling: function() { var scrollingMode = this.option("scrolling.mode"); return "virtual" === scrollingMode || "infinite" === scrollingMode }, _scrollBy: function(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(eventArgs) { var pageIndex = this._dataController.pageIndex(), pageCount = this._dataController.pageCount(), pagingEnabled = this.option("paging.enabled"), isPageUp = "pageUp" === eventArgs.key, 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(eventArgs, isEditing) { var rowIndex = this._getFocusedRowIndex(), $target = $(eventArgs.originalEvent && eventArgs.originalEvent.target); if (this.option("selection") && "none" !== this.option("selection").mode && !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(eventArgs, isEditing) { if (!isEditing && eventArgs.ctrl && !eventArgs.alt && "multiple" === this.option("selection.mode") && this.option("selection.allowSelectAll")) { this._selectionController.selectAll(); eventArgs.originalEvent.preventDefault() } }, _isInsideEditForm: function(element) { return $(element).closest("." + this.addWidgetPrefix(EDIT_FORM_CLASS)).length > 0 }, _isMasterDetailCell: function(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($nextCell) { if (!this._isInsideEditForm($nextCell) && $nextCell) { this._applyTabIndexToElement($nextCell) } }, _handleTabKeyOnMasterDetailCell: function(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(eventArgs, isEditing) { var $cell, 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; 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 (void 0 === this._focusedCellPosition.rowIndex && $(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($cell) { if (this._isCellValid($cell)) { this._focus($cell); return true } }, _getElementType: function(target) { return $(target).is("tr") ? "row" : "cell" }, _focusEditFormCell: function($cell) { if ($cell.hasClass(MASTER_DETAIL_CELL_CLASS)) { this.getController("editorFactory").focus($cell, true) } }, _escapeKeyHandler: function(eventArgs, isEditing) { if (isEditing) { var $cell = this._getCellElementFromTarget(eventArgs.originalEvent.target); this._updateFocusedCellPosition($cell); if (!this._isRowEditMode()) { if ("cell" === this._editingController.getEditMode()) { this._editingController.cancelEditData() } else { this._editingController.closeEditCell() } } else { this._focusEditFormCell($cell); this._editingController.cancelEditData() } eventArgs.originalEvent.preventDefault() } }, _ctrlFKeyHandler: function(eventArgs) { if (eventArgs.ctrl && this.option("searchPanel") && this.option("searchPanel").visible) { this._headerPanel.focus(); eventArgs.originalEvent.preventDefault() } }, _keyDownHandler: function(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 } if (needStopPropagation) { e.originalEvent.stopPropagation() } } }, _isLastRow: function(rowIndex) { if (this._isVirtualScrolling()) { return rowIndex >= this._dataController.totalItemsCount() - 1 } return rowIndex === this.getController("data").items().length - 1 }, _getNextCell: function(keyCode, elementType, cellPosition) { var rowIndex, newFocusedCellPosition, $cell, $row, focusedCellPosition = cellPosition || this._focusedCellPosition, includeCommandCells = inArray(keyCode, ["next", "previous"]) > -1, isLastCellOnDirection = "previous" === keyCode ? this._isFirstValidCell(focusedCellPosition) : this._isLastValidCell(focusedCellPosition); 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(cellPosition, elementType, code) { var visibleColumnsCount, columnIndex = cellPosition.columnIndex, rowIndex = cellPosition.rowIndex; if (void 0 === cellPosition.rowIndex && "next" === code) { return { columnIndex: 0, rowIndex: 0 } } switch (code) { case "nextInRow": case "next": visibleColumnsCount = this._getVisibleColumnCount(); if (columnIndex < visibleColumnsCount - 1 && !this._isLastValidCell({ columnIndex: columnIndex, rowIndex: rowIndex }) && "row" !== elementType) { columnIndex++ } else { if (!this._isLastRow(rowIndex) && "next" === code) { columnIndex = 0; rowIndex++ } } break; case "previousInRow": case "previous": if (columnIndex > 0 && !this._isFirstValidCell({ columnIndex: columnIndex, rowIndex: rowIndex }) && "row" !== elementType) { columnIndex-- } else { if (rowIndex > 0 && "previous" === code) { 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 } return { columnIndex: columnIndex, rowIndex: rowIndex } }, _isFirstValidCell: function(cellPosition) { var isFirstValidCell = false; if (0 === cellPosition.rowIndex && cellPosition.columnIndex >= 0) { isFirstValidCell = isFirstValidCell || !this._haveValidCellBeforePosition(cellPosition) } return isFirstValidCell }, _haveValidCellBeforePosition: function(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(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() { return this.getController("columns").getVisibleColumns().length }, _isCellInRow: function(cellPosition, includeCommandCells) { var columnIndex = cellPosition.columnIndex, visibleColumnsCount = this._getVisibleColumnCount(); return includeCommandCells ? columnIndex >= 0 && columnIndex <= visibleColumnsCount - 1 : columnIndex > 0 && columnIndex < visibleColumnsCount - 1 }, _resetFocusedCell: function() { 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(target) { return $(target).closest("." + ROW_CLASS + "> td") }, init: function() { 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($element, offset) { var scrollable = this._focusedView.getScrollable(); scrollable && scrollable.update(); scrollable && scrollable.scrollToElement($element, offset) }, focus: function(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() { return this._focusedView }, _getFocusedViewByElement: function($element) { var condition = function(view) { return $element.closest(view._$element).length }; return this._getFocusedViewByCondition(condition) }, _getFocusedViewByCondition: function(conditionFunction) { var focusView; each(this._focusedViews, function(index, view) { if (conditionFunction(view)) { focusView = { viewIndex: index, view: view }; return false } }); return focusView }, focusViewByName: function(viewName) { var view = this._getFocusedViewByName(viewName); this._focusView(view.view, view.viewIndex) }, setupFocusedView: function() { if (this.option("useKeyboard") && !isDefined(this._focusedView)) { this.focusViewByName("rowsView") } }, _getFocusedViewByName: function(viewName) { var condition = function(view) { return view.name === viewName }; return this._getFocusedViewByCondition(condition) }, optionChanged: function(args) { var that = this; switch (args.name) { case "useKeyboard": args.handled = true; break; default: that.callBase(args) } }, dispose: function() { 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() { return { useKeyboard: true } }, controllers: { keyboardNavigation: KeyboardNavigationController }, extenders: { views: { rowsView: { renderFocusState: function() { var $row, $cell, that = this, cellElements = that.getCellElements(0), keyboardNavigation = that.getController("keyboardNavigation"), tabIndex = that.option("tabIndex"), oldFocusedView = keyboardNavigation._focusedView, $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() { this.callBase.apply(this, arguments); this.renderFocusState() }, _renderCore: function(change) { this.callBase(change); this.renderFocusState() } } }, controllers: { editing: { editCell: function(rowIndex, columnIndex) { var isCellEditing = this.callBase(rowIndex, columnIndex), keyboardNavigationController = this.getController("keyboardNavigation"); if (isCellEditing) { keyboardNavigationController.setupFocusedView() } return isCellEditing }, editRow: function(rowIndex) { if (this.option("editing.mode") === EDIT_MODE_FORM) { this._keyboardNavigationController._resetFocusedCell() } this.callBase(rowIndex) }, addRow: function(parentKey) { this.getController("keyboardNavigation").setupFocusedView(); this.callBase.apply(this, arguments) }, getFocusedCellInRow: function(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() { this.callBase(); this._keyboardNavigationController = this.getController("keyboardNavigation") } } } } };