angular-ui / ui-grid

UI Grid: an Angular Data Grid
http://ui-grid.info
MIT License
5.39k stars 2.47k forks source link

Navigating grid rows with up/down keyboard arrow keys #2362

Open alexandery opened 9 years ago

alexandery commented 9 years ago

Unless I'm missing something, I don't seem to be able to navigate rows in the grid using my keyboard. Is this supported? If so, what do I need to set to enable the functionality?

PaulL1 commented 9 years ago

The cellNav feature is what enables navigation using the keyboard.

alexandery commented 9 years ago

@PaulL1

Thanks for you reply. However the cellNav feature is not what would enable me to navigate rows - it forces me to navigate cells (which confuses users :( - this is the biggest issue with this approach ) plus breaks my code by not reporting when a row selection has changed. Plus, in some cases I need to be able to select multiple rows which makes this more complicated.

Sure, I can use this workaround and adjust all grids to work with cellNav cell change event, but I believe that another feature (rowNav?) should be a part of the solution and take care of this functionality at a row level. What do you think?

PaulL1 commented 9 years ago

That would be a potential addition, although there is an element of feature bloat that we get if we keep adding features for every little thing. The two options would be:

Probably the choice between the two would depend a little on how much shared logic there was.

nippur72 commented 9 years ago

Can we have this issue marked as enhancement / feature? Focusing and navigating the whole row is very helpful as allows the ui-grid to be used as an enhanced substitute of the <select> tag.

I've played with cellnav.js and it seems to me that it needs to be changed only in one place (the logic being the same). In the CELL_NAV_EVENT instead of focusing when the cell matches, do focus when the row matches. Something like this:

            // rowNav = enables navigating by rows instead by cell
            if ((rowNav && rowCol.row === $scope.row) || 
               (!rowNav && rowCol.row === $scope.row && rowCol.col === $scope.col)) {
              setFocused();
              if(rowCol.col === $scope.col) {            
                // This cellNav event came from a keydown event so we can safely refocus
                if (rowCol.hasOwnProperty('eventType') && rowCol.eventType === uiGridCellNavConstants.EVENT_TYPE.KEYDOWN) {
                  $elm.find('div')[0].focus();
                }
              }
            }
PaulL1 commented 9 years ago

I'm happy to reopen, and also happy to accept a PR with this functionality.

longshot5112 commented 9 years ago

@nippur72 Where did you define rowNav?

nippur72 commented 9 years ago

@longshot5112 for my test I hardcoded it manually e.g.var rowNav = true;

smithscripts commented 9 years ago

Has anyone had any luck implementing a solution for this yet. @nippur72 I've tried you suggestion and it seems to mostly work, but when you get to the bottom row the scrolling does not align with the row that is selected. Especially if you are holding down the down arrow key.

alexandery commented 9 years ago

@smithscripts Sorry, I sort of dropped this. Seemed like ui-grid guys were not interested in building this out and I simply didn't have time to invest into building it out myself. I'd love to have it, but it's a nice-to-have for me at this moment.

chyzwar commented 9 years ago

I would like to see this. UI Grid is perfect for me but I need to have elegant way to map rows to underlining models. In my case at least row navigation would be better because row in grid equal to model instance.

I will try maybe to implement this and do PR but I am struggling at Uni and my day-job is pushing for deadlines.

gokhanoner commented 9 years ago

Hi,

You can achieve this by doing below:

$scope.gridApi.cellNav.on.navigate($scope,function(newRowCol, oldRowCol){
  $scope.gridApi.selection.selectRow(newRowCol.row.entity);
})
anticommander commented 8 years ago

Thank you @gokhanoner! Works perfectly!

shayvt commented 8 years ago

If you're using the cellNav, you won't be able to select cells text. If you want key up/down navigation without the cellNav, I wrote a directive based on the cellNav functionality:

function uiGridKeyNav($compile, gridUtil) {

    return {

        require: '^uiGrid',
        scope: false,
        link: function ($scope, $elm, $attrs, uiGridCtrl) {
            var grid = uiGridCtrl.grid;
            var focuser = $compile('<div class="ui-grid-focuser" role="region" aria-live="assertive" aria-atomic="false" tabindex="0" aria-controls="' + grid.id + '-aria-speakable ' + grid.id + '-grid-container' + '" aria-owns="' + grid.id + '-grid-container' + '"></div>')($scope);
            $elm.append(focuser);

            $elm.bind('click', function () {
                gridUtil.focus.byElement(focuser[0]);
            });

            focuser.bind('keydown', function (e) {
                $scope.$apply(function () {
                    var selectedEntities,
                        visibleRows,
                        selectedIndex;

                    if (e.keyCode !== 38 && e.keyCode !== 40)
                        return;

                    selectedEntities = grid.api.selection.getSelectedRows();

                    if (selectedEntities.length === 1) {
                        visibleRows = grid.getVisibleRows();

                        selectedIndex = visibleRows.map(function (item) {
                            return item.entity;
                        }).indexOf(selectedEntities[0]);

                        if (selectedIndex < 0)
                            return;

                        if (e.keyCode === 38 && selectedIndex > 0)
                            --selectedIndex;

                        if (e.keyCode === 40 && selectedIndex < grid.options.data.length - 1)
                            ++selectedIndex;

                        grid.api.selection.selectRowByVisibleIndex(selectedIndex);
                        grid.api.core.scrollToIfNecessary(visibleRows[selectedIndex], grid.columns[0]);
                    }
                });
            });
        }
    };
}

var commonDirectivesModule = angular.module('common-directives');

commonDirectivesModule.directive('uiGridKeyNav', ['$compile', 'gridUtil', uiGridKeyNav]);`

Using the directive on the grid element:

<div ui-grid="vm.options" ui-grid-selection ui-grid-key-nav>

lentyaishe commented 8 years ago

The best solution that worked for me was updating the original ui-grid.js code and replacing 1 line of code in the following section:

$scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown) {
            var isFocused = grid.cellNav.focusedCells.some(function(focusedRowCol, index){
                return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
            });
            if (isFocused){
              setFocused();
            } else {
              clearFocus();
            }
          });

You'll need to replace: return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col); with: return (focusedRowCol.row === $scope.row);

That's it. The rest is done by the original code.

KurtMar commented 8 years ago

I used the directive provided by @lotri1 (Thanks!). I made some modifications:

Here's the code:

myAppModule.directive('uiGridKeyNav', ['$compile', 'gridUtil', function($compile, gridUtil) {
    return {
        require: '^uiGrid',
        scope: false,
        link: function ($scope, $elm, $attrs, uiGridCtrl) {
            var grid = uiGridCtrl.grid;
            var focuser = $compile('<div class="ui-grid-focuser" role="region" aria-live="assertive" aria-atomic="false" tabindex="0" aria-controls="' + grid.id + '-aria-speakable ' + grid.id + '-grid-container' + '" aria-owns="' + grid.id + '-grid-container' + '"></div>')($scope);
            $elm.append(focuser);

            $elm.bind('click', function () {
                gridUtil.focus.byElement(focuser[0]);
            });

            focuser.bind('keydown', function (e) {
                $scope.$apply(function () {
                    var selectedEntities,
                        visibleRows,
                        selectedIndex;

                    if (e.keyCode !== 38 && e.keyCode !== 40 && e.keyCode !== 37 && e.keyCode !== 39 
                            && e.keyCode !== 33 && e.keyCode !== 34 && e.keyCode !== 13)
                        return;

                    selectedEntities = grid.api.selection.getSelectedRows();

                    if (selectedEntities.length === 1) {
                        visibleRows = grid.getVisibleRows();

                        selectedIndex = visibleRows.map(function (item) {
                            return item.entity;
                        }).indexOf(selectedEntities[0]);

                        if (selectedIndex < 0)
                            return;
                        // Enter
                        if (e.keyCode === 13) {
                            // You might want to do something here like:
                            //grid.api.selection.unSelectRow(selectedEntities[0], e);
                            // This way you can pass the enter key to your gridApi.selection.on.rowSelectionChanged event handler when the the row is reselected again below
                        // Left
                        } else if (e.keyCode === 37) {
                            grid.api.treeBase.collapseRow(grid.rowHashMap.get(selectedEntities[0]));
                        // Right
                        } else if (e.keyCode === 39) {
                            grid.api.treeBase.expandRow(grid.rowHashMap.get(selectedEntities[0]));
                        // Up
                        } else if (e.keyCode === 38 && selectedIndex > 0) {
                            --selectedIndex;
                        // Down
                        } else if (e.keyCode === 40 && selectedIndex < grid.getVisibleRows().length - 1) {
                            ++selectedIndex;
                        // pgUp 
                        } else if (e.keyCode === 33 && selectedIndex > 0) {
                            selectedIndex = selectedIndex - 15;
                            if (selectedIndex < 0) {
                                selectedIndex = 0;
                            }
                        // pgDown
                        } else if (e.keyCode === 34 && selectedIndex < grid.getVisibleRows().length - 1) {
                            selectedIndex = selectedIndex + 15;
                            if (selectedIndex > grid.getVisibleRows().length - 1) {
                                selectedIndex = grid.getVisibleRows().length - 1;
                            }
                        } 

                        grid.api.selection.selectRowByVisibleIndex(selectedIndex, e);
                        grid.api.core.scrollToIfNecessary(visibleRows[selectedIndex], grid.columns[0]);
                    }
                });
            });
        }
    };
}]);
stndbye commented 8 years ago

Thanks for the key directive!

Just in case its of any use to anyone, changing $elm.bind like this will allow using the column filters:

var clickTarget = getEventTarget(e);
if (clickTarget.tagName !== 'INPUT') {
   gridUtil.focus.byElement(focuser[0]);
}

... where getEventTarget is:

function getEventTarget(e) {
  e = e || window.event;
  return e.target || e.srcElement;
}
ciceroaferreira commented 8 years ago

@lentyaishe Great solution, it works fine for me.

Thanks

alib192 commented 7 years ago

@lentyaishe I really like your directive but it doesn't work for me with the down arrow key, it moves down once but not again. Am I missing something? Also would there be a way to introduce a 'ctrl'/'shift' and arrow option?

Thanks

lentyaishe commented 7 years ago

@alib192 Unfortunately I'm quite far now from the project in which I used my workaround. Try to check that you've followed the instructions correctly and didn't miss something since it worked for @ciceroaferreira. Or just try to debug and check why it works only once.

sthakker7 commented 6 years ago

Any update on this issue? I believe this bug still exists in the latest version of angular ui grid, which is 4.0.7. Issue: Unable to use cell navigation and drag-and-drop at the same time!!

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 6 years ago

This issue has been automatically closed because it has not had recent activity. If you believe that this is still an issue in the latest version, feel free to re-open it. Thank you for your contributions.

mportuga commented 6 years ago

Note: add help wanted to this issue to prevent automatic closing