Skip to content

Commit 4f07b15

Browse files
author
Ditommaso, Daniel
committed
fix(ui-grid-column-menu.js): Added keyboard navigation to column menu
Provided keydown handlers for uiGridColumnMenu so you can tab-cycle through the menu items correctly. Escape also now closes an open menu. fix #5075
1 parent 29ab28d commit 4f07b15

File tree

2 files changed

+131
-30
lines changed

2 files changed

+131
-30
lines changed

src/js/core/directives/ui-grid-column-menu.js

+39-18
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,8 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $documen
387387

388388

389389
$scope.$on('menu-hidden', function() {
390+
var menuItems = angular.element($elm[0].querySelector('.ui-grid-menu-items'))[0];
391+
390392
$elm[0].removeAttribute('style');
391393

392394
if ( $scope.hideThenShow ){
@@ -403,6 +405,13 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $documen
403405
gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false);
404406
}
405407
}
408+
409+
if (menuItems) {
410+
menuItems.onkeydown = null;
411+
angular.forEach(menuItems.children, function removeHandlers(item) {
412+
item.onkeydown = null;
413+
});
414+
}
406415
});
407416

408417
$scope.$on('menu-shown', function() {
@@ -413,9 +422,7 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $documen
413422
gridUtil.focus.bySelector($document, '.ui-grid-menu-items .ui-grid-menu-item:not(.ng-hide)', true);
414423
delete $scope.colElementPosition;
415424
delete $scope.columnElement;
416-
});
417-
$timeout(function() {
418-
addKeydownHandlers(getVisibleMenuItems());
425+
addKeydownHandlersToMenu();
419426
}, 0, false);
420427
});
421428

@@ -438,27 +445,41 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $documen
438445
$scope.hideMenu();
439446
};
440447

441-
function addKeydownHandlers(menuItems) {
442-
// Add escape keydown to every item.
443-
// Loop focus on last item
444-
// loop focus on first item Shift + tab
445-
menuItems[0].onkeydown = function(event) {
446-
alert('hello');
447-
};
448-
}
449-
450-
function getVisibleMenuItems() {
451-
var menuItems = angular.element($elm[0].querySelector('.ui-grid-menu-items')),
448+
function addKeydownHandlersToMenu() {
449+
var menu = angular.element($elm[0].querySelector('.ui-grid-menu-items'))[0],
450+
menuItems,
452451
visibleMenuItems = [];
453452

454-
if (menuItems[0]) {
455-
angular.forEach(menuItems[0].children, function(item) {
456-
if (item.offsetParent !== null && !item.firstChild.className.includes('ng-hide')) {
453+
if (menu) {
454+
menu.onkeydown = function closeMenu(event) {
455+
if (event.keyCode === uiGridConstants.keymap.ESC) {
456+
event.preventDefault();
457+
$scope.hideMenu();
458+
}
459+
};
460+
461+
menuItems = menu.querySelectorAll('.ui-grid-menu-item:not(.ng-hide)');
462+
angular.forEach(menuItems, function filterVisibleItems(item) {
463+
if (item.offsetParent !== null) {
457464
this.push(item);
458465
}
459466
}, visibleMenuItems);
460467

461-
return visibleMenuItems;
468+
if (visibleMenuItems.length) {
469+
visibleMenuItems[0].onkeydown = function firstItemHandler(event) {
470+
circularFocusHandler(event, event.shiftKey, visibleMenuItems.length - 1);
471+
};
472+
visibleMenuItems[visibleMenuItems.length - 1].onkeydown = function lastItemHandler(event) {
473+
circularFocusHandler(event, !event.shiftKey, 0);
474+
};
475+
}
476+
}
477+
478+
function circularFocusHandler(event, shiftKeyStatus, index) {
479+
if (event.keyCode === uiGridConstants.keymap.TAB && shiftKeyStatus) {
480+
event.preventDefault();
481+
visibleMenuItems[index].focus();
482+
}
462483
}
463484
}
464485

test/unit/core/directives/ui-grid-column-menu.spec.js

+92-12
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
describe('ui-grid-column-menu uiGridColumnMenuService', function() {
22
'use strict';
33

4-
var uiGridColumnMenuService, gridClassFactory, gridUtil, grid, $rootScope, $scope;
5-
6-
beforeEach(function() {
7-
module('ui.grid');
8-
9-
inject(function(_uiGridColumnMenuService_, _gridClassFactory_, _gridUtil_, _$rootScope_) {
10-
uiGridColumnMenuService = _uiGridColumnMenuService_;
11-
gridClassFactory = _gridClassFactory_;
12-
gridUtil = _gridUtil_;
13-
$rootScope = _$rootScope_;
14-
});
15-
});
4+
var $rootScope,
5+
$scope,
6+
grid,
7+
gridClassFactory,
8+
gridUtil,
9+
uiGridColumnMenuService,
10+
uiGridConstants;
11+
12+
beforeEach(function() {
13+
module('ui.grid');
14+
15+
inject(function( _$rootScope_, _gridClassFactory_, _gridUtil_, _uiGridColumnMenuService_, _uiGridConstants_) {
16+
$rootScope = _$rootScope_;
17+
gridClassFactory = _gridClassFactory_;
18+
gridUtil = _gridUtil_;
19+
uiGridColumnMenuService = _uiGridColumnMenuService_;
20+
uiGridConstants = _uiGridConstants_;
21+
});
22+
});
1623

1724
describe('uiGridColumnMenuService', function() {
1825
beforeEach(function() {
@@ -550,11 +557,14 @@ describe('ui-grid-column-menu uiGridColumnMenuService', function() {
550557

551558
$('body').append(uiGrid);
552559
$('.ui-grid-column-menu-button').click();
560+
561+
spyOn(uiGridColumnMenuService, 'repositionMenu').and.callFake(angular.noop);
553562
});
554563
afterEach(function() {
555564
$scope.$destroy();
556565
uiGrid.remove();
557566
});
567+
558568
it('should raise the sortChanged event when unsort is clicked', function() {
559569
$($('.ui-grid-menu-item')[2]).click();
560570
$timeout.flush();
@@ -568,5 +578,75 @@ describe('ui-grid-column-menu uiGridColumnMenuService', function() {
568578
expect(columnVisibilityChanged).toHaveBeenCalled();
569579
});
570580

581+
describe('uiGridMenu keydown handlers', function() {
582+
beforeEach(function() {
583+
$timeout.flush();
584+
});
585+
586+
it('should add keydown handler to ui-grid-menu', function() {
587+
var menu = $('.ui-grid-menu-items')[0];
588+
589+
expect(menu.onkeydown).not.toBe(null);
590+
});
591+
it('should add keydown handlers to first and last visible menu-items', function() {
592+
var items = $('.ui-grid-menu-item');
593+
594+
for(var i = 0; i < items.length; i++) {
595+
if(i === 0 || i === items.length - 1) {
596+
expect(items[i].onkeydown).not.toBe(null);
597+
} else {
598+
expect(items[i].onkeydown).toBe(null);
599+
}
600+
}
601+
});
602+
describe('menu keydown handler', function() {
603+
it('should close menu when escape key is pressed', function() {
604+
var menu = $('.ui-grid-menu-items'),
605+
event = jQuery.Event('keydown', {keyCode: uiGridConstants.keymap.ESC});
606+
607+
expect(menu[0].onkeydown).not.toBe(null);
608+
menu.trigger(event);
609+
$timeout.flush();
610+
expect(menu[0].onkeydown).toBe(null);
611+
});
612+
});
613+
describe('menu-item keydown handler', function() {
614+
it('should focus on last visible item when shift tab is pressed on first visible item', function() {
615+
var event = jQuery.Event('keydown', {keyCode: uiGridConstants.keymap.TAB, shiftKey: true}),
616+
items = $('.ui-grid-menu-item');
617+
618+
spyOn(items[items.length - 1], 'focus');
619+
items[0].onkeydown(event);
620+
expect(items[items.length - 1].focus).toHaveBeenCalled();
621+
});
622+
it('should focus on first visible item when tab is pressed on last visible item', function() {
623+
var event = jQuery.Event('keydown', {keyCode: uiGridConstants.keymap.TAB, shiftKey: false}),
624+
items = $('.ui-grid-menu-item');
625+
626+
spyOn(items[0], 'focus');
627+
items[items.length - 1].onkeydown(event);
628+
expect(items[0].focus).toHaveBeenCalled();
629+
});
630+
});
631+
describe('closing ui-grid-column-menu', function() {
632+
it('should remove keydown handlers from menu-items', function() {
633+
var menu = $('.ui-grid-menu-items'),
634+
items = $('.ui-grid-menu-item'),
635+
event = jQuery.Event('keydown', {keyCode: uiGridConstants.keymap.ESC});
636+
637+
expect(menu[0].onkeydown).not.toBe(null);
638+
expect(items[0].onkeydown).not.toBe(null);
639+
expect(items[items.length - 1].onkeydown).not.toBe(null);
640+
641+
menu.trigger(event);
642+
$timeout.flush();
643+
644+
expect(menu[0].onkeydown).toBe(null);
645+
angular.forEach($('.ui-grid-menu-item'), function(item) {
646+
expect(item.onkeydown).toBe(null);
647+
});
648+
});
649+
});
650+
});
571651
});
572652
});

0 commit comments

Comments
 (0)