diff --git a/misc/tutorial/102_sorting.ngdoc b/misc/tutorial/102_sorting.ngdoc index ed99db2197..6f4f25207d 100644 --- a/misc/tutorial/102_sorting.ngdoc +++ b/misc/tutorial/102_sorting.ngdoc @@ -238,8 +238,8 @@ columnDef option will cause sorting to be applied after the `cellFilters` are ap }); it('click one menu, then click another menu, expect undisplay and redisplay on second click', function() { - grid1.expectVisibleColumnMenuItems( 0, 3 ); - grid1.expectVisibleColumnMenuItems( 1, 3 ); + grid1.expectVisibleColumnMenuItems( 0, 4 ); + grid1.expectVisibleColumnMenuItems( 1, 4 ); }); it('toggle gender, expect Alexander Foley to move around', function() { diff --git a/misc/tutorial/303_customizing_column_menu.ngdoc b/misc/tutorial/303_customizing_column_menu.ngdoc index ab82190136..813d1c0ea7 100644 --- a/misc/tutorial/303_customizing_column_menu.ngdoc +++ b/misc/tutorial/303_customizing_column_menu.ngdoc @@ -128,8 +128,8 @@ See the example below for usage. }) }); - it('2 menu items in second column, implying no hide option and no remove sort option', function () { - gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 1, 2 ); + it('3 menu items in second column, implying no hide option and no remove sort option', function () { + gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 1, 3 ); }); it('Long press opens menu in second column', function () { @@ -181,14 +181,14 @@ See the example below for usage. }); }); - it('6 visible items in the third column, implying hide option', function () { - gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 2, 6 ); + it('7 visible items in the third column, implying hide option', function () { + gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 2, 7 ); }); - it('click header to sort third column, 7 visible items in the third column, implying remove sort option', function () { + it('click header to sort third column, 8 visible items in the third column, implying remove sort option', function () { gridTestUtils.clickHeaderCell( 'grid1', 2 ) .then(function () { - gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 2, 7 ); + gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 2, 8 ); }); }); }); diff --git a/misc/tutorial/401_AllFeatures.ngdoc b/misc/tutorial/401_AllFeatures.ngdoc index f5dfa5c903..33b7d2cc76 100644 --- a/misc/tutorial/401_AllFeatures.ngdoc +++ b/misc/tutorial/401_AllFeatures.ngdoc @@ -112,7 +112,7 @@ All features are enabled to get an idea of performance it('should not duplicate the menu options for pinning when resizing a column', function () { element( by.id('refreshButton') ).click(); gridTestUtils.resizeHeaderCell( 'grid1', 1 ); - gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 1, 11); + gridTestUtils.expectVisibleColumnMenuItems( 'grid1', 1, 12); }); }); diff --git a/src/js/core/directives/ui-grid-column-menu.js b/src/js/core/directives/ui-grid-column-menu.js index 344e345746..96f77e9f11 100644 --- a/src/js/core/directives/ui-grid-column-menu.js +++ b/src/js/core/directives/ui-grid-column-menu.js @@ -1,7 +1,7 @@ (function(){ angular.module('ui.grid') -.service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil', +.service('uiGridColumnMenuService', [ 'i18nService', 'uiGridConstants', 'gridUtil', function ( i18nService, uiGridConstants, gridUtil ) { /** * @ngdoc service @@ -16,12 +16,12 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @ngdoc method * @methodOf ui.grid.service:uiGridColumnMenuService * @name initialize - * @description Sets defaults, puts a reference to the $scope on + * @description Sets defaults, puts a reference to the $scope on * the uiGridController * @param {$scope} $scope the $scope from the uiGridColumnMenu * @param {controller} uiGridCtrl the uiGridController for the grid * we're on - * + * */ initialize: function( $scope, uiGridCtrl ){ $scope.grid = uiGridCtrl.grid; @@ -29,12 +29,12 @@ function ( i18nService, uiGridConstants, gridUtil ) { // Store a reference to this link/controller in the main uiGrid controller // to allow showMenu later uiGridCtrl.columnMenuScope = $scope; - + // Save whether we're shown or not so the columns can check $scope.menuShown = false; }, - - + + /** * @ngdoc method * @methodOf ui.grid.service:uiGridColumnMenuService @@ -45,8 +45,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @param {$scope} $scope the $scope from the uiGridColumnMenu * @param {controller} uiGridCtrl the uiGridController for the grid * we're on - * - */ + * + */ setColMenuItemWatch: function ( $scope ){ var deregFunction = $scope.$watch('col.menuItems', function (n, o) { if (typeof(n) !== 'undefined' && n && angular.isArray(n)) { @@ -62,9 +62,9 @@ function ( i18nService, uiGridConstants, gridUtil ) { else { $scope.menuItems = $scope.defaultMenuItems; } - }); - - $scope.$on( '$destroy', deregFunction ); + }); + + $scope.$on( '$destroy', deregFunction ); }, @@ -81,8 +81,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @name sortable * @description determines whether this column is sortable * @param {$scope} $scope the $scope from the uiGridColumnMenu - * - */ + * + */ sortable: function( $scope ) { if ( $scope.grid.options.enableSorting && typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.enableSorting) { return true; @@ -91,31 +91,31 @@ function ( i18nService, uiGridConstants, gridUtil ) { return false; } }, - + /** * @ngdoc method * @methodOf ui.grid.service:uiGridColumnMenuService * @name isActiveSort - * @description determines whether the requested sort direction is current active, to + * @description determines whether the requested sort direction is current active, to * allow highlighting in the menu * @param {$scope} $scope the $scope from the uiGridColumnMenu * @param {string} direction the direction that we'd have selected for us to be active - * - */ + * + */ isActiveSort: function( $scope, direction ){ - return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' && + return (typeof($scope.col) !== 'undefined' && typeof($scope.col.sort) !== 'undefined' && typeof($scope.col.sort.direction) !== 'undefined' && $scope.col.sort.direction === direction); - + }, - + /** * @ngdoc method * @methodOf ui.grid.service:uiGridColumnMenuService * @name suppressRemoveSort * @description determines whether we should suppress the removeSort option * @param {$scope} $scope the $scope from the uiGridColumnMenu - * - */ + * + */ suppressRemoveSort: function( $scope ) { if ($scope.col && $scope.col.suppressRemoveSort) { return true; @@ -123,7 +123,7 @@ function ( i18nService, uiGridConstants, gridUtil ) { else { return false; } - }, + }, /** @@ -139,8 +139,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @name hideable * @description determines whether a column can be hidden, by checking the enableHiding columnDef option * @param {$scope} $scope the $scope from the uiGridColumnMenu - * - */ + * + */ hideable: function( $scope ) { if (typeof($scope.col) !== 'undefined' && $scope.col && $scope.col.colDef && $scope.col.colDef.enableHiding === false ) { return false; @@ -148,7 +148,7 @@ function ( i18nService, uiGridConstants, gridUtil ) { else { return true; } - }, + }, /** @@ -157,8 +157,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @name getDefaultMenuItems * @description returns the default menu items for a column menu * @param {$scope} $scope the $scope from the uiGridColumnMenu - * - */ + * + */ getDefaultMenuItems: function( $scope ){ return [ { @@ -197,8 +197,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { $scope.unsortColumn(); }, shown: function() { - return service.sortable( $scope ) && - typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' && + return service.sortable( $scope ) && + typeof($scope.col) !== 'undefined' && (typeof($scope.col.sort) !== 'undefined' && typeof($scope.col.sort.direction) !== 'undefined') && $scope.col.sort.direction !== null && !service.suppressRemoveSort( $scope ); } @@ -213,10 +213,20 @@ function ( i18nService, uiGridConstants, gridUtil ) { $event.stopPropagation(); $scope.hideColumn(); } + }, + { + title: i18nService.getSafeText('columnMenu.close'), + screenReaderOnly: true, + shown: function(){ + return true; + }, + action: function($event){ + $event.stopPropagation(); + } } ]; }, - + /** * @ngdoc method @@ -228,8 +238,8 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @param {GridCol} column the column we want to position below * @param {element} $columnElement the column element we want to position below * @returns {hash} containing left, top, offset, height, width - * - */ + * + */ getColumnElementPosition: function( $scope, column, $columnElement ){ var positionData = {}; positionData.left = $columnElement[0].offsetLeft; @@ -244,16 +254,16 @@ function ( i18nService, uiGridConstants, gridUtil ) { positionData.height = gridUtil.elementHeight($columnElement, true); positionData.width = gridUtil.elementWidth($columnElement, true); - + return positionData; }, - + /** * @ngdoc method * @methodOf ui.grid.service:uiGridColumnMenuService * @name repositionMenu - * @description Reposition the menu below the new column. If the menu has no child nodes + * @description Reposition the menu below the new column. If the menu has no child nodes * (i.e. it's not currently visible) then we guess it's width at 100, we'll be called again * later to fix it * @param {$scope} $scope the $scope from the uiGridColumnMenu @@ -261,15 +271,15 @@ function ( i18nService, uiGridConstants, gridUtil ) { * @param {hash} positionData a hash containing left, top, offset, height, width * @param {element} $elm the column menu element that we want to reposition * @param {element} $columnElement the column element that we want to reposition underneath - * - */ + * + */ repositionMenu: function( $scope, column, positionData, $elm, $columnElement ) { var menu = $elm[0].querySelectorAll('.ui-grid-menu'); var containerId = column.renderContainer ? column.renderContainer : 'body'; var renderContainer = column.grid.renderContainers[containerId]; - // It's possible that the render container of the column we're attaching to is - // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft + // It's possible that the render container of the column we're attaching to is + // offset from the grid (i.e. pinned containers), we need to get the difference in the offsetLeft // between the render container and the grid var renderContainerElm = gridUtil.closestElm($columnElement, '.ui-grid-render-container'); var renderContainerOffset = renderContainerElm.getBoundingClientRect().left - $scope.grid.element[0].getBoundingClientRect().left; @@ -279,14 +289,14 @@ function ( i18nService, uiGridConstants, gridUtil ) { // default value the last width for _this_ column, otherwise last width for _any_ column, otherwise default to 170 var myWidth = column.lastMenuWidth ? column.lastMenuWidth : ( $scope.lastMenuWidth ? $scope.lastMenuWidth : 170); var paddingRight = column.lastMenuPaddingRight ? column.lastMenuPaddingRight : ( $scope.lastMenuPaddingRight ? $scope.lastMenuPaddingRight : 10); - + if ( menu.length !== 0 ){ - var mid = menu[0].querySelectorAll('.ui-grid-menu-mid'); + var mid = menu[0].querySelectorAll('.ui-grid-menu-mid'); if ( mid.length !== 0 && !angular.element(mid).hasClass('ng-hide') ) { myWidth = gridUtil.elementWidth(menu, true); $scope.lastMenuWidth = myWidth; column.lastMenuWidth = myWidth; - + // TODO(c0bra): use padding-left/padding-right based on document direction (ltr/rtl), place menu on proper side // Get the column menu right padding paddingRight = parseInt(gridUtil.getStyles(angular.element(menu)[0])['paddingRight'], 10); @@ -294,7 +304,7 @@ function ( i18nService, uiGridConstants, gridUtil ) { column.lastMenuPaddingRight = paddingRight; } } - + var left = positionData.left + renderContainerOffset - containerScrollLeft + positionData.parentLeft + positionData.width - myWidth + paddingRight; if (left < positionData.offset){ left = positionData.offset; @@ -302,32 +312,32 @@ function ( i18nService, uiGridConstants, gridUtil ) { $elm.css('left', left + 'px'); $elm.css('top', (positionData.top + positionData.height) + 'px'); - } + } }; - + return service; }]) -.directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', -function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { +.directive('uiGridColumnMenu', ['$timeout', 'gridUtil', 'uiGridConstants', 'uiGridColumnMenuService', '$document', +function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService, $document) { /** * @ngdoc directive * @name ui.grid.directive:uiGridColumnMenu * @description Provides the column menu framework, leverages uiGridMenu underneath - * + * */ var uiGridColumnMenu = { priority: 0, scope: true, - require: '?^uiGrid', + require: '^uiGrid', templateUrl: 'ui-grid/uiGridColumnMenu', replace: true, link: function ($scope, $elm, $attrs, uiGridCtrl) { var self = this; - + uiGridColumnMenuService.initialize( $scope, uiGridCtrl ); $scope.defaultMenuItems = uiGridColumnMenuService.getDefaultMenuItems( $scope ); @@ -336,15 +346,15 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { $scope.menuItems = $scope.defaultMenuItems; uiGridColumnMenuService.setColMenuItemWatch( $scope ); - + /** * @ngdoc method * @methodOf ui.grid.directive:uiGridColumnMenu * @name showMenu * @description Shows the column menu. If the menu is already displayed it * calls the menu to ask it to hide (it will animate), then it repositions the menu - * to the right place whilst hidden (it will make an assumption on menu width), - * then it asks the menu to show (it will animate), then it repositions the menu again + * to the right place whilst hidden (it will make an assumption on menu width), + * then it asks the menu to show (it will animate), then it repositions the menu again * once we can calculate it's size. * @param {GridCol} column the column we want to position below * @param {element} $columnElement the column element we want to position below @@ -371,8 +381,7 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { $scope.colElement = $columnElement; $scope.colElementPosition = colElementPosition; $scope.$broadcast('show-menu', { originalEvent: event }); - } - + } }; @@ -386,15 +395,13 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { * an infinite loop */ $scope.hideMenu = function( broadcastTrigger ) { - // delete $scope.col; $scope.menuShown = false; - if ( !broadcastTrigger ){ $scope.$broadcast('hide-menu'); } }; - + $scope.$on('menu-hidden', function() { if ( $scope.hideThenShow ){ delete $scope.hideThenShow; @@ -405,9 +412,14 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { $scope.menuShown = true; } else { $scope.hideMenu( true ); + + if ($scope.col) { + //Focus on the menu button + gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + $scope.col.getColClass()+ ' .ui-grid-column-menu-button', $scope.col.grid, false); + } } }); - + $scope.$on('menu-shown', function() { $timeout( function() { uiGridColumnMenuService.repositionMenu( $scope, $scope.col, $scope.colElementPosition, $elm, $scope.colElement ); @@ -416,7 +428,7 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { }, 200); }); - + /* Column methods */ $scope.sortColumn = function (event, dir) { event.stopPropagation(); @@ -435,6 +447,58 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { $scope.hideMenu(); }; + //Since we are hiding this column the default hide action will fail so we need to focus somewhere else. + var setFocusOnHideColumn = function(){ + $timeout(function(){ + // Get the UID of the first + var focusToGridMenu = function(){ + return gridUtil.focus.byId('grid-menu', $scope.grid); + }; + + var thisIndex; + $scope.grid.columns.some(function(element, index){ + if (angular.equals(element, $scope.col)) { + thisIndex = index; + return true; + } + }); + + var previousVisibleCol; + // Try and find the next lower or nearest column to focus on + $scope.grid.columns.some(function(element, index){ + if (!element.visible){ + return false; + } // This columns index is below the current column index + else if ( index < thisIndex){ + previousVisibleCol = element; + } // This elements index is above this column index and we haven't found one that is lower + else if ( index > thisIndex && !previousVisibleCol) { + // This is the next best thing + previousVisibleCol = element; + // We've found one so use it. + return true; + } // We've reached an element with an index above this column and the previousVisibleCol variable has been set + else if (index > thisIndex && previousVisibleCol) { + // We are done. + return true; + } + }); + // If found then focus on it + if (previousVisibleCol){ + var colClass = previousVisibleCol.getColClass(); + gridUtil.focus.bySelector($document, '.ui-grid-header-cell.' + colClass+ ' .ui-grid-header-cell-primary-focus', true).then(angular.noop, function(reason){ + if (reason !== 'canceled'){ // If this is canceled then don't perform the action + //The fallback action is to focus on the grid menu + return focusToGridMenu(); + } + }); + } else { + // Fallback action to focus on the grid menu + focusToGridMenu(); + } + }); + }; + $scope.hideColumn = function () { $scope.col.colDef.visible = false; $scope.col.visible = false; @@ -442,15 +506,18 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { $scope.grid.queueGridRefresh(); $scope.hideMenu(); $scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN ); - $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col ); + $scope.grid.api.core.raise.columnVisibilityChanged( $scope.col ); + + // We are hiding so the default action of focusing on the button that opened this menu will fail. + setFocusOnHideColumn(); }; }, - - - + + + controller: ['$scope', function ($scope) { var self = this; - + $scope.$watch('menuItems', function (n, o) { self.menuItems = n; }); @@ -461,4 +528,4 @@ function ($timeout, gridUtil, uiGridConstants, uiGridColumnMenuService) { }]); -})(); \ No newline at end of file +})(); diff --git a/src/js/core/directives/ui-grid-menu-button.js b/src/js/core/directives/ui-grid-menu-button.js index 34d3644690..983eb29064 100644 --- a/src/js/core/directives/ui-grid-menu-button.js +++ b/src/js/core/directives/ui-grid-menu-button.js @@ -16,7 +16,7 @@ angular.module('ui.grid') * @name initialize * @description Sets up the gridMenu. Most importantly, sets our * scope onto the grid object as grid.gridMenuScope, allowing us - * to operate when passed only the grid. Second most importantly, + * to operate when passed only the grid. Second most importantly, * we register the 'addToGridMenu' and 'removeFromGridMenu' methods * on the core api. * @param {$scope} $scope the scope of this gridMenu @@ -26,7 +26,7 @@ angular.module('ui.grid') grid.gridMenuScope = $scope; $scope.grid = grid; $scope.registeredMenuItems = []; - + // not certain this is needed, but would be bad to create a memory leak $scope.$on('$destroy', function() { if ( $scope.grid && $scope.grid.gridMenuScope ){ @@ -39,7 +39,7 @@ angular.module('ui.grid') $scope.registeredMenuItems = null; } }); - + $scope.registeredMenuItems = []; /** @@ -53,13 +53,13 @@ angular.module('ui.grid') * in the menu when. (Noting that in most cases the shown and active functions * provide a better way to handle visibility of menu items) * @param {Grid} grid the grid on which we are acting - * @param {array} items menu items in the format as described in the tutorial, with + * @param {array} items menu items in the format as described in the tutorial, with * the added note that if you want to use remove you must also specify an `id` field, * which is provided when you want to remove an item. The id should be unique. - * + * */ grid.api.registerMethod( 'core', 'addToGridMenu', service.addToGridMenu ); - + /** * @ngdoc function * @name removeFromGridMenu @@ -69,12 +69,12 @@ angular.module('ui.grid') * the specified id is not found * @param {Grid} grid the grid on which we are acting * @param {string} id the id we'd like to remove from the menu - * + * */ grid.api.registerMethod( 'core', 'removeFromGridMenu', service.removeFromGridMenu ); }, - - + + /** * @ngdoc function * @name addToGridMenu @@ -86,10 +86,10 @@ angular.module('ui.grid') * in the menu when. (Noting that in most cases the shown and active functions * provide a better way to handle visibility of menu items) * @param {Grid} grid the grid on which we are acting - * @param {array} items menu items in the format as described in the tutorial, with + * @param {array} items menu items in the format as described in the tutorial, with * the added note that if you want to use remove you must also specify an `id` field, * which is provided when you want to remove an item. The id should be unique. - * + * */ addToGridMenu: function( grid, menuItems ) { if ( !angular.isArray( menuItems ) ) { @@ -101,9 +101,9 @@ angular.module('ui.grid') } else { gridUtil.logError( 'Asked to addToGridMenu, but gridMenuScope not present. Timing issue? Please log issue with ui-grid'); } - } + } }, - + /** * @ngdoc function @@ -116,18 +116,18 @@ angular.module('ui.grid') * aren't. * @param {Grid} grid the grid on which we are acting * @param {string} id the id we'd like to remove from the menu - * - */ + * + */ removeFromGridMenu: function( grid, id ){ var foundIndex = -1; - + if ( grid && grid.gridMenuScope ){ grid.gridMenuScope.registeredMenuItems.forEach( function( value, index ) { if ( value.id === id ){ if (foundIndex > -1) { gridUtil.logError( 'removeFromGridMenu: found multiple items with the same id, removing only the last' ); } else { - + foundIndex = index; } } @@ -138,19 +138,19 @@ angular.module('ui.grid') grid.gridMenuScope.registeredMenuItems.splice( foundIndex, 1 ); } }, - - + + /** * @ngdoc array * @name gridMenuCustomItems * @propertyOf ui.grid.class:GridOptions * @description (optional) An array of menu items that should be added to * the gridMenu. Follow the format documented in the tutorial for column - * menu customisation. The context provided to the action function will - * include context.grid. An alternative if working with dynamic menus is to use the + * menu customisation. The context provided to the action function will + * include context.grid. An alternative if working with dynamic menus is to use the * provided api - core.addToGridMenu and core.removeFromGridMenu, which handles * some of the management of items for you. - * + * */ /** * @ngdoc boolean @@ -158,7 +158,7 @@ angular.module('ui.grid') * @propertyOf ui.grid.class:GridOptions * @description true by default, whether the grid menu should allow hide/show * of columns - * + * */ /** * @ngdoc method @@ -166,54 +166,54 @@ angular.module('ui.grid') * @name getMenuItems * @description Decides the menu items to show in the menu. This is a * combination of: - * - * - the default menu items that are always included, + * + * - the default menu items that are always included, * - any menu items that have been provided through the addMenuItem api. These * are typically added by features within the grid * - any menu items included in grid.options.gridMenuCustomItems. These can be * changed dynamically, as they're always recalculated whenever we show the * menu - * @param {$scope} $scope the scope of this gridMenu, from which we can find all + * @param {$scope} $scope the scope of this gridMenu, from which we can find all * the information that we need - * @returns {array} an array of menu items that can be shown + * @returns {array} an array of menu items that can be shown */ getMenuItems: function( $scope ) { var menuItems = [ // this is where we add any menu items we want to always include ]; - + if ( $scope.grid.options.gridMenuCustomItems ){ - if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){ - gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not'); + if ( !angular.isArray( $scope.grid.options.gridMenuCustomItems ) ){ + gridUtil.logError( 'gridOptions.gridMenuCustomItems must be an array, and is not'); } else { menuItems = menuItems.concat( $scope.grid.options.gridMenuCustomItems ); } } - + menuItems = menuItems.concat( $scope.registeredMenuItems ); - + if ( $scope.grid.options.gridMenuShowHideColumns !== false ){ menuItems = menuItems.concat( service.showHideColumns( $scope ) ); } - + menuItems.sort(function(a, b){ return a.order - b.order; }); - + return menuItems; }, - - + + /** * @ngdoc array * @name gridMenuTitleFilter * @propertyOf ui.grid.class:GridOptions - * @description (optional) A function that takes a title string + * @description (optional) A function that takes a title string * (usually the col.displayName), and converts it into a display value. The function * must return either a string or a promise. - * + * * Used for internationalization of the grid menu column names - for angular-translate - * you can pass $translate as the function, for i18nService you can pass getSafeText as the + * you can pass $translate as the function, for i18nService you can pass getSafeText as the * function * @example *
@@ -237,15 +237,15 @@ angular.module('ui.grid')
       if ( !$scope.grid.options.columnDefs || $scope.grid.options.columnDefs.length === 0 || $scope.grid.columns.length === 0 ) {
         return showHideColumns;
       }
-      
+
       // add header for columns
       showHideColumns.push({
         title: i18nService.getSafeText('gridMenu.columns'),
         order: 300
       });
-      
-      $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };  
-      
+
+      $scope.grid.options.gridMenuTitleFilter = $scope.grid.options.gridMenuTitleFilter ? $scope.grid.options.gridMenuTitleFilter : function( title ) { return title; };
+
       $scope.grid.options.columnDefs.forEach( function( colDef, index ){
         if ( colDef.enableHiding !== false ){
           // add hide menu item - shows an OK icon as we only show when column is already visible
@@ -285,23 +285,23 @@ angular.module('ui.grid')
       });
       return showHideColumns;
     },
-    
-    
+
+
     /**
      * @ngdoc method
      * @methodOf ui.grid.gridMenuService
      * @name setMenuItemTitle
      * @description Handles the response from gridMenuTitleFilter, adding it directly to the menu
      * item if it returns a string, otherwise waiting for the promise to resolve or reject then
-     * putting the result into the title 
+     * putting the result into the title
      * @param {object} menuItem the menuItem we want to put the title on
      * @param {object} colDef the colDef from which we can get displayName, name or field
      * @param {Grid} grid the grid, from which we can get the options.gridMenuTitleFilter
-     * 
+     *
      */
     setMenuItemTitle: function( menuItem, colDef, grid ){
       var title = grid.options.gridMenuTitleFilter( colDef.displayName || gridUtil.readableColumnName(colDef.name) || colDef.field );
-      
+
       if ( typeof(title) === 'string' ){
         menuItem.title = title;
       } else if ( title.then ){
@@ -326,38 +326,42 @@ angular.module('ui.grid')
      * provided a context that has on it a gridColumn, which is the column that
      * we'll operate upon.  We change the visibility, and refresh the grid as appropriate
      * @param {GridCol} gridCol the column that we want to toggle
-     * 
+     *
      */
     toggleColumnVisibility: function( gridCol ) {
-      gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined ); 
-      
+      gridCol.colDef.visible = !( gridCol.colDef.visible === true || gridCol.colDef.visible === undefined );
+
       gridCol.grid.refresh();
       gridCol.grid.api.core.notifyDataChange( uiGridConstants.dataChange.COLUMN );
       gridCol.grid.api.core.raise.columnVisibilityChanged( gridCol );
     }
   };
-  
+
   return service;
 }])
 
 
 
-.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 
-function (gridUtil, uiGridConstants, uiGridGridMenuService) {
+.directive('uiGridMenuButton', ['gridUtil', 'uiGridConstants', 'uiGridGridMenuService', 'i18nService',
+function (gridUtil, uiGridConstants, uiGridGridMenuService, i18nService) {
 
   return {
     priority: 0,
     scope: true,
-    require: ['?^uiGrid'],
+    require: ['^uiGrid'],
     templateUrl: 'ui-grid/ui-grid-menu-button',
     replace: true,
 
-
     link: function ($scope, $elm, $attrs, controllers) {
       var uiGridCtrl = controllers[0];
 
+      // For the aria label
+      $scope.i18n = {
+        aria: i18nService.getSafeText('gridMenu.aria')
+      };
+
       uiGridGridMenuService.initialize($scope, uiGridCtrl.grid);
-      
+
       $scope.shown = false;
 
       $scope.toggleMenu = function () {
@@ -370,13 +374,14 @@ function (gridUtil, uiGridConstants, uiGridGridMenuService) {
           $scope.shown = true;
         }
       };
-      
+
       $scope.$on('menu-hidden', function() {
         $scope.shown = false;
+        gridUtil.focus.bySelector($elm, '.ui-grid-icon-container');
       });
     }
   };
 
 }]);
 
-})();
\ No newline at end of file
+})();
diff --git a/src/js/core/directives/ui-grid-menu.js b/src/js/core/directives/ui-grid-menu.js
index 5f088a4813..2dad20b1a0 100644
--- a/src/js/core/directives/ui-grid-menu.js
+++ b/src/js/core/directives/ui-grid-menu.js
@@ -30,8 +30,8 @@
  */
 angular.module('ui.grid')
 
-.directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants',
-function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
+.directive('uiGridMenu', ['$compile', '$timeout', '$window', '$document', 'gridUtil', 'uiGridConstants', 'i18nService',
+function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants, i18nService) {
   var uiGridMenu = {
     priority: 0,
     scope: {
@@ -47,6 +47,10 @@ function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
       var menuMid;
       var $animate;
 
+      $scope.i18n = {
+        close: i18nService.getSafeText('columnMenu.close')
+      };
+
     // *** Show/Hide functions ******
       self.showMenu = $scope.showMenu = function(event, args) {
         if ( !$scope.shown ){
@@ -87,6 +91,8 @@ function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
         $timeout(function() {
           angular.element(document).on(docEventType, applyHideMenu);
         });
+        //automatically set the focus to the first button element in the now open menu.
+        gridUtil.focus.bySelector($elm, 'button[type=button]', true);
       };
 
 
@@ -174,11 +180,12 @@ function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
       shown: '=',
       context: '=',
       templateUrl: '=',
-      leaveOpen: '='
+      leaveOpen: '=',
+      screenReaderOnly: '='
     },
     require: ['?^uiGrid', '^uiGridMenu'],
     templateUrl: 'ui-grid/uiGridMenuItem',
-    replace: true,
+    replace: false,
     compile: function($elm, $attrs) {
       return {
         pre: function ($scope, $elm, $attrs, controllers) {
@@ -221,7 +228,7 @@ function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
           };
 
           $scope.itemAction = function($event,title) {
-            // gridUtil.logDebug('itemAction');
+            gridUtil.logDebug('itemAction');
             $event.stopPropagation();
 
             if (typeof($scope.action) === 'function') {
@@ -240,6 +247,13 @@ function ($compile, $timeout, $window, $document, gridUtil, uiGridConstants) {
 
               if ( !$scope.leaveOpen ){
                 $scope.$emit('hide-menu');
+              } else {
+                /*
+                 * XXX: Fix after column refactor
+                 * Ideally the focus would remain on the item.
+                 * However, since there are two menu items that have their 'show' property toggled instead. This is a quick fix.
+                 */
+                gridUtil.focus.bySelector(angular.element(gridUtil.closestElm($elm, ".ui-grid-menu-items")), 'button[type=button]', true);
               }
             }
           };
diff --git a/src/less/menu.less b/src/less/menu.less
index b4a717c9bd..9d4c1d062a 100644
--- a/src/less/menu.less
+++ b/src/less/menu.less
@@ -39,6 +39,20 @@
 
   .rounded(@gridBorderRadius);
   .box-shadow(e("0 10px 20px rgba(0, 0, 0, 0.2), inset 0 12px 12px -14px rgba(0, 0, 0, 0.2)"));
+
+  // Small hidden close button that only appears when focused.
+  .ui-grid-menu-close-button {
+    position: absolute;
+    right: 0px;
+    top: 0px;
+    #ui-grid-twbs > .btn();
+    #ui-grid-twbs > .button-size(1px; 1px; 10px; 1; 2px);
+    #ui-grid-twbs > .button-variant(transparent, transparent, transparent);
+    > i {
+      opacity: 0.75;
+      color: black;
+    }
+  }
 }
 
 .ui-grid-menu .ui-grid-menu-inner ul {
@@ -47,23 +61,29 @@
   list-style-type: none;
 
   li {
-    padding: 8px;
-    cursor: pointer;
-
-    // Show a shadow when hovering over a menu item
-    &:hover {
-      // background-color: negation(@headerBackgroundColor, #fff);
-      .inner-shadow(@vertical: 0, @blur: 14px, @alpha: 0.2);
-    }
+    padding: 0px;
+    button {
+      min-width: 100%;
+      padding: 8px;
+      text-align: left;
+      background: transparent;
+      border: none;
 
-    &.ui-grid-menu-item-active {
-      .inner-shadow(@vertical: 0, @blur: 14px, @alpha: 0.2);
-      background-color: @selectedColor;
+      // Show a shadow when hovering over a menu item
+      &:hover,
+      &:focus {
+        // background-color: negation(@headerBackgroundColor, #fff);
+        .inner-shadow(@vertical: 0, @blur: 14px, @alpha: 0.2);
+      }
+      &.ui-grid-menu-item-active {
+        .inner-shadow(@vertical: 0, @blur: 14px, @alpha: 0.2);
+        background-color: @selectedColor;
+      }
     }
   }
 
   // Show a bottom border on all but the last menu item
-  li:not(:last-child) {
+  li:not(:last-child) > button {
     border-bottom: @gridBorderWidth solid @borderColor;
   }
 }
diff --git a/src/templates/ui-grid/ui-grid-menu-button.html b/src/templates/ui-grid/ui-grid-menu-button.html
index 69f1029e98..2a548e3963 100644
--- a/src/templates/ui-grid/ui-grid-menu-button.html
+++ b/src/templates/ui-grid/ui-grid-menu-button.html
@@ -1,6 +1,11 @@
-
-
-   +
+
+  
diff --git a/src/templates/ui-grid/uiGridHeaderCell.html b/src/templates/ui-grid/uiGridHeaderCell.html index 308e36c506..8b72b9cf4d 100644 --- a/src/templates/ui-grid/uiGridHeaderCell.html +++ b/src/templates/ui-grid/uiGridHeaderCell.html @@ -7,7 +7,7 @@
{{ col.displayName CUSTOM_FILTERS }} diff --git a/src/templates/ui-grid/uiGridMenu.html b/src/templates/ui-grid/uiGridMenu.html index bb72ed341f..44ce136c92 100644 --- a/src/templates/ui-grid/uiGridMenu.html +++ b/src/templates/ui-grid/uiGridMenu.html @@ -1,10 +1,23 @@
-
    + +
-
\ No newline at end of file +
diff --git a/src/templates/ui-grid/uiGridMenuItem.html b/src/templates/ui-grid/uiGridMenuItem.html index a7512e9c60..5a18f3f1a9 100644 --- a/src/templates/ui-grid/uiGridMenuItem.html +++ b/src/templates/ui-grid/uiGridMenuItem.html @@ -1 +1,17 @@ -
  • {{ name }}
  • +