| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194 |
- /*!
- * ui-grid - v4.4.6 - 2018-04-06
- * Copyright (c) 2018 ; License: MIT
- */
- (function () {
- 'use strict';
- /**
- * @ngdoc overview
- * @name ui.grid.cellNav
- *
- * @description
- #ui.grid.cellNav
- <div class="alert alert-success" role="alert"><strong>Stable</strong> This feature is stable. There should no longer be breaking api changes without a deprecation warning.</div>
- This module provides cell navigation functionality to UI-Grid.
- */
- var module = angular.module('ui.grid.cellNav', ['ui.grid']);
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.constant:uiGridCellNavConstants
- *
- * @description constants available in cellNav
- */
- module.constant('uiGridCellNavConstants', {
- FEATURE_NAME: 'gridCellNav',
- CELL_NAV_EVENT: 'cellNav',
- direction: {LEFT: 0, RIGHT: 1, UP: 2, DOWN: 3, PG_UP: 4, PG_DOWN: 5},
- EVENT_TYPE: {
- KEYDOWN: 0,
- CLICK: 1,
- CLEAR: 2
- }
- });
- module.factory('uiGridCellNavFactory', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', 'GridRowColumn', '$q',
- function (gridUtil, uiGridConstants, uiGridCellNavConstants, GridRowColumn, $q) {
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.object:CellNav
- * @description returns a CellNav prototype function
- * @param {object} rowContainer container for rows
- * @param {object} colContainer parent column container
- * @param {object} leftColContainer column container to the left of parent
- * @param {object} rightColContainer column container to the right of parent
- */
- var UiGridCellNav = function UiGridCellNav(rowContainer, colContainer, leftColContainer, rightColContainer) {
- this.rows = rowContainer.visibleRowCache;
- this.columns = colContainer.visibleColumnCache;
- this.leftColumns = leftColContainer ? leftColContainer.visibleColumnCache : [];
- this.rightColumns = rightColContainer ? rightColContainer.visibleColumnCache : [];
- this.bodyContainer = rowContainer;
- };
- /** returns focusable columns of all containers */
- UiGridCellNav.prototype.getFocusableCols = function () {
- var allColumns = this.leftColumns.concat(this.columns, this.rightColumns);
- return allColumns.filter(function (col) {
- return col.colDef.allowCellFocus;
- });
- };
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.api:GridRow
- *
- * @description GridRow settings for cellNav feature, these are available to be
- * set only internally (for example, by other features)
- */
- /**
- * @ngdoc object
- * @name allowCellFocus
- * @propertyOf ui.grid.cellNav.api:GridRow
- * @description Enable focus on a cell within this row. If set to false then no cells
- * in this row can be focused - group header rows as an example would set this to false.
- * <br/>Defaults to true
- */
- /** returns focusable rows */
- UiGridCellNav.prototype.getFocusableRows = function () {
- return this.rows.filter(function(row) {
- return row.allowCellFocus !== false;
- });
- };
- UiGridCellNav.prototype.getNextRowCol = function (direction, curRow, curCol) {
- switch (direction) {
- case uiGridCellNavConstants.direction.LEFT:
- return this.getRowColLeft(curRow, curCol);
- case uiGridCellNavConstants.direction.RIGHT:
- return this.getRowColRight(curRow, curCol);
- case uiGridCellNavConstants.direction.UP:
- return this.getRowColUp(curRow, curCol);
- case uiGridCellNavConstants.direction.DOWN:
- return this.getRowColDown(curRow, curCol);
- case uiGridCellNavConstants.direction.PG_UP:
- return this.getRowColPageUp(curRow, curCol);
- case uiGridCellNavConstants.direction.PG_DOWN:
- return this.getRowColPageDown(curRow, curCol);
- }
- };
- UiGridCellNav.prototype.initializeSelection = function () {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- if (focusableCols.length === 0 || focusableRows.length === 0) {
- return null;
- }
- var curRowIndex = 0;
- var curColIndex = 0;
- return new GridRowColumn(focusableRows[0], focusableCols[0]); //return same row
- };
- UiGridCellNav.prototype.getRowColLeft = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 1
- if (curColIndex === -1) {
- curColIndex = 1;
- }
- var nextColIndex = curColIndex === 0 ? focusableCols.length - 1 : curColIndex - 1;
- //get column to left
- if (nextColIndex >= curColIndex) {
- // On the first row
- // if (curRowIndex === 0 && curColIndex === 0) {
- // return null;
- // }
- if (curRowIndex === 0) {
- return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
- }
- else {
- //up one row and far right column
- return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[nextColIndex]);
- }
- }
- else {
- return new GridRowColumn(curRow, focusableCols[nextColIndex]);
- }
- };
- UiGridCellNav.prototype.getRowColRight = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 0
- if (curColIndex === -1) {
- curColIndex = 0;
- }
- var nextColIndex = curColIndex === focusableCols.length - 1 ? 0 : curColIndex + 1;
- if (nextColIndex <= curColIndex) {
- if (curRowIndex === focusableRows.length - 1) {
- return new GridRowColumn(curRow, focusableCols[nextColIndex]); //return same row
- }
- else {
- //down one row and far left column
- return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[nextColIndex]);
- }
- }
- else {
- return new GridRowColumn(curRow, focusableCols[nextColIndex]);
- }
- };
- UiGridCellNav.prototype.getRowColDown = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 0
- if (curColIndex === -1) {
- curColIndex = 0;
- }
- if (curRowIndex === focusableRows.length - 1) {
- return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
- }
- else {
- //down one row
- return new GridRowColumn(focusableRows[curRowIndex + 1], focusableCols[curColIndex]);
- }
- };
- UiGridCellNav.prototype.getRowColPageDown = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 0
- if (curColIndex === -1) {
- curColIndex = 0;
- }
- var pageSize = this.bodyContainer.minRowsToRender();
- if (curRowIndex >= focusableRows.length - pageSize) {
- return new GridRowColumn(focusableRows[focusableRows.length - 1], focusableCols[curColIndex]); //return last row
- }
- else {
- //down one page
- return new GridRowColumn(focusableRows[curRowIndex + pageSize], focusableCols[curColIndex]);
- }
- };
- UiGridCellNav.prototype.getRowColUp = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 0
- if (curColIndex === -1) {
- curColIndex = 0;
- }
- if (curRowIndex === 0) {
- return new GridRowColumn(curRow, focusableCols[curColIndex]); //return same row
- }
- else {
- //up one row
- return new GridRowColumn(focusableRows[curRowIndex - 1], focusableCols[curColIndex]);
- }
- };
- UiGridCellNav.prototype.getRowColPageUp = function (curRow, curCol) {
- var focusableCols = this.getFocusableCols();
- var focusableRows = this.getFocusableRows();
- var curColIndex = focusableCols.indexOf(curCol);
- var curRowIndex = focusableRows.indexOf(curRow);
- //could not find column in focusable Columns so set it to 0
- if (curColIndex === -1) {
- curColIndex = 0;
- }
- var pageSize = this.bodyContainer.minRowsToRender();
- if (curRowIndex - pageSize < 0) {
- return new GridRowColumn(focusableRows[0], focusableCols[curColIndex]); //return first row
- }
- else {
- //up one page
- return new GridRowColumn(focusableRows[curRowIndex - pageSize], focusableCols[curColIndex]);
- }
- };
- return UiGridCellNav;
- }]);
- /**
- * @ngdoc service
- * @name ui.grid.cellNav.service:uiGridCellNavService
- *
- * @description Services for cell navigation features. If you don't like the key maps we use,
- * or the direction cells navigation, override with a service decorator (see angular docs)
- */
- module.service('uiGridCellNavService', ['gridUtil', 'uiGridConstants', 'uiGridCellNavConstants', '$q', 'uiGridCellNavFactory', 'GridRowColumn', 'ScrollEvent',
- function (gridUtil, uiGridConstants, uiGridCellNavConstants, $q, UiGridCellNav, GridRowColumn, ScrollEvent) {
- var service = {
- initializeGrid: function (grid) {
- grid.registerColumnBuilder(service.cellNavColumnBuilder);
- /**
- * @ngdoc object
- * @name ui.grid.cellNav:Grid.cellNav
- * @description cellNav properties added to grid class
- */
- grid.cellNav = {};
- grid.cellNav.lastRowCol = null;
- grid.cellNav.focusedCells = [];
- service.defaultGridOptions(grid.options);
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.api:PublicApi
- *
- * @description Public Api for cellNav feature
- */
- var publicApi = {
- events: {
- cellNav: {
- /**
- * @ngdoc event
- * @name navigate
- * @eventOf ui.grid.cellNav.api:PublicApi
- * @description raised when the active cell is changed
- * <pre>
- * gridApi.cellNav.on.navigate(scope,function(newRowcol, oldRowCol){})
- * </pre>
- * @param {object} newRowCol new position
- * @param {object} oldRowCol old position
- */
- navigate: function (newRowCol, oldRowCol) {},
- /**
- * @ngdoc event
- * @name viewPortKeyDown
- * @eventOf ui.grid.cellNav.api:PublicApi
- * @description is raised when the viewPort receives a keyDown event. Cells never get focus in uiGrid
- * due to the difficulties of setting focus on a cell that is not visible in the viewport. Use this
- * event whenever you need a keydown event on a cell
- * <br/>
- * @param {object} event keydown event
- * @param {object} rowCol current rowCol position
- */
- viewPortKeyDown: function (event, rowCol) {},
- /**
- * @ngdoc event
- * @name viewPortKeyPress
- * @eventOf ui.grid.cellNav.api:PublicApi
- * @description is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
- * due to the difficulties of setting focus on a cell that is not visible in the viewport. Use this
- * event whenever you need a keypress event on a cell
- * <br/>
- * @param {object} event keypress event
- * @param {object} rowCol current rowCol position
- */
- viewPortKeyPress: function (event, rowCol) {}
- }
- },
- methods: {
- cellNav: {
- /**
- * @ngdoc function
- * @name scrollToFocus
- * @methodOf ui.grid.cellNav.api:PublicApi
- * @description brings the specified row and column into view, and sets focus
- * to that cell
- * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus
- * @param {object} colDef to make visible and set focus
- * @returns {promise} a promise that is resolved after any scrolling is finished
- */
- scrollToFocus: function (rowEntity, colDef) {
- return service.scrollToFocus(grid, rowEntity, colDef);
- },
- /**
- * @ngdoc function
- * @name getFocusedCell
- * @methodOf ui.grid.cellNav.api:PublicApi
- * @description returns the current (or last if Grid does not have focus) focused row and column
- * <br> value is null if no selection has occurred
- */
- getFocusedCell: function () {
- return grid.cellNav.lastRowCol;
- },
- /**
- * @ngdoc function
- * @name getCurrentSelection
- * @methodOf ui.grid.cellNav.api:PublicApi
- * @description returns an array containing the current selection
- * <br> array is empty if no selection has occurred
- */
- getCurrentSelection: function () {
- return grid.cellNav.focusedCells;
- },
- /**
- * @ngdoc function
- * @name rowColSelectIndex
- * @methodOf ui.grid.cellNav.api:PublicApi
- * @description returns the index in the order in which the GridRowColumn was selected, returns -1 if the GridRowColumn
- * isn't selected
- * @param {object} rowCol the rowCol to evaluate
- */
- rowColSelectIndex: function (rowCol) {
- //return gridUtil.arrayContainsObjectWithProperty(grid.cellNav.focusedCells, 'col.uid', rowCol.col.uid) &&
- var index = -1;
- for (var i = 0; i < grid.cellNav.focusedCells.length; i++) {
- if (grid.cellNav.focusedCells[i].col.uid === rowCol.col.uid &&
- grid.cellNav.focusedCells[i].row.uid === rowCol.row.uid) {
- index = i;
- break;
- }
- }
- return index;
- }
- }
- }
- };
- grid.api.registerEventsFromObject(publicApi.events);
- grid.api.registerMethodsFromObject(publicApi.methods);
- },
- defaultGridOptions: function (gridOptions) {
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.api:GridOptions
- *
- * @description GridOptions for cellNav feature, these are available to be
- * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
- */
- /**
- * @ngdoc object
- * @name modifierKeysToMultiSelectCells
- * @propertyOf ui.grid.cellNav.api:GridOptions
- * @description Enable multiple cell selection only when using the ctrlKey or shiftKey.
- * <br/>Defaults to false
- */
- gridOptions.modifierKeysToMultiSelectCells = gridOptions.modifierKeysToMultiSelectCells === true;
- /**
- * @ngdoc array
- * @name keyDownOverrides
- * @propertyOf ui.grid.cellNav.api:GridOptions
- * @description An array of event objects to override on keydown. If an event is overridden, the viewPortKeyDown event will
- * be raised with the overridden events, allowing custom keydown behavior.
- * <br/>Defaults to []
- */
- gridOptions.keyDownOverrides = gridOptions.keyDownOverrides || [];
- },
- /**
- * @ngdoc service
- * @name decorateRenderContainers
- * @methodOf ui.grid.cellNav.service:uiGridCellNavService
- * @description decorates grid renderContainers with cellNav functions
- */
- decorateRenderContainers: function (grid) {
- var rightContainer = grid.hasRightContainer() ? grid.renderContainers.right : null;
- var leftContainer = grid.hasLeftContainer() ? grid.renderContainers.left : null;
- if (leftContainer !== null) {
- grid.renderContainers.left.cellNav = new UiGridCellNav(grid.renderContainers.body, leftContainer, rightContainer, grid.renderContainers.body);
- }
- if (rightContainer !== null) {
- grid.renderContainers.right.cellNav = new UiGridCellNav(grid.renderContainers.body, rightContainer, grid.renderContainers.body, leftContainer);
- }
- grid.renderContainers.body.cellNav = new UiGridCellNav(grid.renderContainers.body, grid.renderContainers.body, leftContainer, rightContainer);
- },
- /**
- * @ngdoc service
- * @name getDirection
- * @methodOf ui.grid.cellNav.service:uiGridCellNavService
- * @description determines which direction to for a given keyDown event
- * @returns {uiGridCellNavConstants.direction} direction
- */
- getDirection: function (evt) {
- if (evt.keyCode === uiGridConstants.keymap.LEFT ||
- (evt.keyCode === uiGridConstants.keymap.TAB && evt.shiftKey)) {
- return uiGridCellNavConstants.direction.LEFT;
- }
- if (evt.keyCode === uiGridConstants.keymap.RIGHT ||
- evt.keyCode === uiGridConstants.keymap.TAB) {
- return uiGridCellNavConstants.direction.RIGHT;
- }
- if (evt.keyCode === uiGridConstants.keymap.UP ||
- (evt.keyCode === uiGridConstants.keymap.ENTER && evt.shiftKey) ) {
- return uiGridCellNavConstants.direction.UP;
- }
- if (evt.keyCode === uiGridConstants.keymap.PG_UP){
- return uiGridCellNavConstants.direction.PG_UP;
- }
- if (evt.keyCode === uiGridConstants.keymap.DOWN ||
- evt.keyCode === uiGridConstants.keymap.ENTER && !(evt.ctrlKey || evt.altKey)) {
- return uiGridCellNavConstants.direction.DOWN;
- }
- if (evt.keyCode === uiGridConstants.keymap.PG_DOWN){
- return uiGridCellNavConstants.direction.PG_DOWN;
- }
- return null;
- },
- /**
- * @ngdoc service
- * @name cellNavColumnBuilder
- * @methodOf ui.grid.cellNav.service:uiGridCellNavService
- * @description columnBuilder function that adds cell navigation properties to grid column
- * @returns {promise} promise that will load any needed templates when resolved
- */
- cellNavColumnBuilder: function (colDef, col, gridOptions) {
- var promises = [];
- /**
- * @ngdoc object
- * @name ui.grid.cellNav.api:ColumnDef
- *
- * @description Column Definitions for cellNav feature, these are available to be
- * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
- */
- /**
- * @ngdoc object
- * @name allowCellFocus
- * @propertyOf ui.grid.cellNav.api:ColumnDef
- * @description Enable focus on a cell within this column.
- * <br/>Defaults to true
- */
- colDef.allowCellFocus = colDef.allowCellFocus === undefined ? true : colDef.allowCellFocus;
- return $q.all(promises);
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.cellNav.service:uiGridCellNavService
- * @name scrollToFocus
- * @description Scroll the grid such that the specified
- * row and column is in view, and set focus to the cell in that row and column
- * @param {Grid} grid the grid you'd like to act upon, usually available
- * from gridApi.grid
- * @param {object} rowEntity gridOptions.data[] array instance to make visible and set focus to
- * @param {object} colDef to make visible and set focus to
- * @returns {promise} a promise that is resolved after any scrolling is finished
- */
- scrollToFocus: function (grid, rowEntity, colDef) {
- var gridRow = null, gridCol = null;
- if (typeof(rowEntity) !== 'undefined' && rowEntity !== null) {
- gridRow = grid.getRow(rowEntity);
- }
- if (typeof(colDef) !== 'undefined' && colDef !== null) {
- gridCol = grid.getColumn(colDef.name ? colDef.name : colDef.field);
- }
- return grid.api.core.scrollToIfNecessary(gridRow, gridCol).then(function () {
- var rowCol = { row: gridRow, col: gridCol };
- // Broadcast the navigation
- if (gridRow !== null && gridCol !== null) {
- grid.cellNav.broadcastCellNav(rowCol);
- }
- });
- },
- /**
- * @ngdoc method
- * @methodOf ui.grid.cellNav.service:uiGridCellNavService
- * @name getLeftWidth
- * @description Get the current drawn width of the columns in the
- * grid up to the numbered column, and add an apportionment for the
- * column that we're on. So if we are on column 0, we want to scroll
- * 0% (i.e. exclude this column from calc). If we're on the last column
- * we want to scroll to 100% (i.e. include this column in the calc). So
- * we include (thisColIndex / totalNumberCols) % of this column width
- * @param {Grid} grid the grid you'd like to act upon, usually available
- * from gridApi.grid
- * @param {GridColumn} upToCol the column to total up to and including
- */
- getLeftWidth: function (grid, upToCol) {
- var width = 0;
- if (!upToCol) {
- return width;
- }
- var lastIndex = grid.renderContainers.body.visibleColumnCache.indexOf( upToCol );
- // total column widths up-to but not including the passed in column
- grid.renderContainers.body.visibleColumnCache.forEach( function( col, index ) {
- if ( index < lastIndex ){
- width += col.drawnWidth;
- }
- });
- // pro-rata the final column based on % of total columns.
- var percentage = lastIndex === 0 ? 0 : (lastIndex + 1) / grid.renderContainers.body.visibleColumnCache.length;
- width += upToCol.drawnWidth * percentage;
- return width;
- }
- };
- return service;
- }]);
- /**
- * @ngdoc directive
- * @name ui.grid.cellNav.directive:uiCellNav
- * @element div
- * @restrict EA
- *
- * @description Adds cell navigation features to the grid columns
- *
- * @example
- <example module="app">
- <file name="app.js">
- var app = angular.module('app', ['ui.grid', 'ui.grid.cellNav']);
- app.controller('MainCtrl', ['$scope', function ($scope) {
- $scope.data = [
- { name: 'Bob', title: 'CEO' },
- { name: 'Frank', title: 'Lowly Developer' }
- ];
- $scope.columnDefs = [
- {name: 'name'},
- {name: 'title'}
- ];
- }]);
- </file>
- <file name="index.html">
- <div ng-controller="MainCtrl">
- <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-cellnav></div>
- </div>
- </file>
- </example>
- */
- module.directive('uiGridCellnav', ['gridUtil', 'uiGridCellNavService', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn', '$timeout', '$compile', 'i18nService',
- function (gridUtil, uiGridCellNavService, uiGridCellNavConstants, uiGridConstants, GridRowColumn, $timeout, $compile, i18nService) {
- return {
- replace: true,
- priority: -150,
- require: '^uiGrid',
- scope: false,
- controller: function () {},
- compile: function () {
- return {
- pre: function ($scope, $elm, $attrs, uiGridCtrl) {
- var _scope = $scope;
- var grid = uiGridCtrl.grid;
- uiGridCellNavService.initializeGrid(grid);
- uiGridCtrl.cellNav = {};
- //Ensure that the object has all of the methods we expect it to
- uiGridCtrl.cellNav.makeRowCol = function (obj) {
- if (!(obj instanceof GridRowColumn)) {
- obj = new GridRowColumn(obj.row, obj.col);
- }
- return obj;
- };
- uiGridCtrl.cellNav.getActiveCell = function () {
- var elms = $elm[0].getElementsByClassName('ui-grid-cell-focus');
- if (elms.length > 0){
- return elms[0];
- }
- return undefined;
- };
- uiGridCtrl.cellNav.broadcastCellNav = grid.cellNav.broadcastCellNav = function (newRowCol, modifierDown, originEvt) {
- modifierDown = !(modifierDown === undefined || !modifierDown);
- newRowCol = uiGridCtrl.cellNav.makeRowCol(newRowCol);
- uiGridCtrl.cellNav.broadcastFocus(newRowCol, modifierDown, originEvt);
- _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT, newRowCol, modifierDown, originEvt);
- };
- uiGridCtrl.cellNav.clearFocus = grid.cellNav.clearFocus = function () {
- grid.cellNav.focusedCells = [];
- _scope.$broadcast(uiGridCellNavConstants.CELL_NAV_EVENT);
- };
- uiGridCtrl.cellNav.broadcastFocus = function (rowCol, modifierDown, originEvt) {
- modifierDown = !(modifierDown === undefined || !modifierDown);
- rowCol = uiGridCtrl.cellNav.makeRowCol(rowCol);
- var row = rowCol.row,
- col = rowCol.col;
- var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
- if (grid.cellNav.lastRowCol === null || rowColSelectIndex === -1 || (grid.cellNav.lastRowCol.col === col && grid.cellNav.lastRowCol.row === row)) {
- var newRowCol = new GridRowColumn(row, col);
- if (grid.cellNav.lastRowCol === null || grid.cellNav.lastRowCol.row !== newRowCol.row || grid.cellNav.lastRowCol.col !== newRowCol.col || grid.options.enableCellEditOnFocus){
- grid.api.cellNav.raise.navigate(newRowCol, grid.cellNav.lastRowCol, originEvt);
- grid.cellNav.lastRowCol = newRowCol;
- }
- if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells && modifierDown) {
- grid.cellNav.focusedCells.push(rowCol);
- } else {
- grid.cellNav.focusedCells = [rowCol];
- }
- } else if (grid.options.modifierKeysToMultiSelectCells && modifierDown &&
- rowColSelectIndex >= 0) {
- grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
- }
- };
- uiGridCtrl.cellNav.handleKeyDown = function (evt) {
- var direction = uiGridCellNavService.getDirection(evt);
- if (direction === null) {
- return null;
- }
- var containerId = 'body';
- if (evt.uiGridTargetRenderContainerId) {
- containerId = evt.uiGridTargetRenderContainerId;
- }
- // Get the last-focused row+col combo
- var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
- if (lastRowCol) {
- // Figure out which new row+combo we're navigating to
- var rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(direction, lastRowCol.row, lastRowCol.col);
- var focusableCols = uiGridCtrl.grid.renderContainers[containerId].cellNav.getFocusableCols();
- var rowColSelectIndex = uiGridCtrl.grid.api.cellNav.rowColSelectIndex(rowCol);
- // Shift+tab on top-left cell should exit cellnav on render container
- if (
- // Navigating left
- direction === uiGridCellNavConstants.direction.LEFT &&
- // New col is last col (i.e. wrap around)
- rowCol.col === focusableCols[focusableCols.length - 1] &&
- // Staying on same row, which means we're at first row
- rowCol.row === lastRowCol.row &&
- evt.keyCode === uiGridConstants.keymap.TAB &&
- evt.shiftKey
- ) {
- grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
- uiGridCtrl.cellNav.clearFocus();
- return true;
- }
- // Tab on bottom-right cell should exit cellnav on render container
- else if (
- direction === uiGridCellNavConstants.direction.RIGHT &&
- // New col is first col (i.e. wrap around)
- rowCol.col === focusableCols[0] &&
- // Staying on same row, which means we're at first row
- rowCol.row === lastRowCol.row &&
- evt.keyCode === uiGridConstants.keymap.TAB &&
- !evt.shiftKey
- ) {
- grid.cellNav.focusedCells.splice(rowColSelectIndex, 1);
- uiGridCtrl.cellNav.clearFocus();
- return true;
- }
- // Scroll to the new cell, if it's not completely visible within the render container's viewport
- grid.scrollToIfNecessary(rowCol.row, rowCol.col).then(function () {
- uiGridCtrl.cellNav.broadcastCellNav(rowCol, null, evt);
- });
- evt.stopPropagation();
- evt.preventDefault();
- return false;
- }
- };
- },
- post: function ($scope, $elm, $attrs, uiGridCtrl) {
- var _scope = $scope;
- var grid = uiGridCtrl.grid;
- var usesAria = true;
- // Detect whether we are using ngAria
- // (if ngAria module is not used then the stuff inside addAriaLiveRegion
- // is not used and provides extra fluff)
- try {
- angular.module('ngAria');
- }
- catch (err) {
- usesAria = false;
- }
- function addAriaLiveRegion(){
- // Thanks to google docs for the inspiration behind how to do this
- // XXX: Why is this entire mess nessasary?
- // Because browsers take a lot of coercing to get them to read out live regions
- //http://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/
- var ariaNotifierDomElt = '<div ' +
- 'id="' + grid.id +'-aria-speakable" ' +
- 'class="ui-grid-a11y-ariascreenreader-speakable ui-grid-offscreen" ' +
- 'aria-live="assertive" ' +
- 'role="alert" ' +
- 'aria-atomic="true" ' +
- 'aria-hidden="false" ' +
- 'aria-relevant="additions" ' +
- '>' +
- ' ' +
- '</div>';
- var ariaNotifier = $compile(ariaNotifierDomElt)($scope);
- $elm.prepend(ariaNotifier);
- $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, function (evt, rowCol, modifierDown, originEvt) {
- /*
- * If the cell nav event was because of a focus event then we don't want to
- * change the notifier text.
- * Reasoning: Voice Over fires a focus events when moving arround the grid.
- * If the screen reader is handing the grid nav properly then we don't need to
- * use the alert to notify the user of the movement.
- * In all other cases we do want a notification event.
- */
- if (originEvt && originEvt.type === 'focus'){return;}
- function setNotifyText(text){
- if (text === ariaNotifier.text().trim()){return;}
- ariaNotifier[0].style.clip = 'rect(0px,0px,0px,0px)';
- /*
- * This is how google docs handles clearing the div. Seems to work better than setting the text of the div to ''
- */
- ariaNotifier[0].innerHTML = "";
- ariaNotifier[0].style.visibility = 'hidden';
- ariaNotifier[0].style.visibility = 'visible';
- if (text !== ''){
- ariaNotifier[0].style.clip = 'auto';
- /*
- * The space after the text is something that google docs does.
- */
- ariaNotifier[0].appendChild(document.createTextNode(text + " "));
- ariaNotifier[0].style.visibility = 'hidden';
- ariaNotifier[0].style.visibility = 'visible';
- }
- }
- function getAppendedColumnHeaderText(col) {
- return ', ' + i18nService.getSafeText('headerCell.aria.column') + ' ' + col.displayName;
- }
- function getCellDisplayValue(currentRowColumn) {
- if (currentRowColumn.col.field === 'selectionRowHeaderCol') {
- // This is the case when the 'selection' feature is used in the grid and the user has moved
- // to or inside of the left grid container which holds the checkboxes for selecting rows.
- // This is necessary for Accessibility. Without this a screen reader cannot determine if the row
- // is or is not currently selected.
- return currentRowColumn.row.isSelected ? i18nService.getSafeText('search.aria.selected') : i18nService.getSafeText('search.aria.notSelected');
- } else {
- return grid.getCellDisplayValue(currentRowColumn.row, currentRowColumn.col);
- }
- }
- var values = [];
- var currentSelection = grid.api.cellNav.getCurrentSelection();
- for (var i = 0; i < currentSelection.length; i++) {
- var cellDisplayValue = getCellDisplayValue(currentSelection[i]) + getAppendedColumnHeaderText(currentSelection[i].col);
- values.push(cellDisplayValue);
- }
- var cellText = values.toString();
- setNotifyText(cellText);
- });
- }
- // Only add the ngAria stuff it will be used
- if (usesAria) {
- addAriaLiveRegion();
- }
- }
- };
- }
- };
- }]);
- module.directive('uiGridRenderContainer', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', '$compile','uiGridCellNavConstants',
- function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, $compile, uiGridCellNavConstants) {
- return {
- replace: true,
- priority: -99999, //this needs to run very last
- require: ['^uiGrid', 'uiGridRenderContainer', '?^uiGridCellnav'],
- scope: false,
- compile: function () {
- return {
- post: function ($scope, $elm, $attrs, controllers) {
- var uiGridCtrl = controllers[0],
- renderContainerCtrl = controllers[1],
- uiGridCellnavCtrl = controllers[2];
- // Skip attaching cell-nav specific logic if the directive is not attached above us
- if (!uiGridCtrl.grid.api.cellNav) { return; }
- var containerId = renderContainerCtrl.containerId;
- var grid = uiGridCtrl.grid;
- //run each time a render container is created
- uiGridCellNavService.decorateRenderContainers(grid);
- // focusser only created for body
- if (containerId !== 'body') {
- return;
- }
- if (uiGridCtrl.grid.options.modifierKeysToMultiSelectCells){
- $elm.attr('aria-multiselectable', true);
- } else {
- $elm.attr('aria-multiselectable', false);
- }
- //add an element with no dimensions that can be used to set focus and capture keystrokes
- 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);
- focuser.on('focus', function (evt) {
- evt.uiGridTargetRenderContainerId = containerId;
- var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
- if (rowCol === null) {
- rowCol = uiGridCtrl.grid.renderContainers[containerId].cellNav.getNextRowCol(uiGridCellNavConstants.direction.DOWN, null, null);
- if (rowCol.row && rowCol.col) {
- uiGridCtrl.cellNav.broadcastCellNav(rowCol);
- }
- }
- });
- uiGridCellnavCtrl.setAriaActivedescendant = function(id){
- $elm.attr('aria-activedescendant', id);
- };
- uiGridCellnavCtrl.removeAriaActivedescendant = function(id){
- if ($elm.attr('aria-activedescendant') === id){
- $elm.attr('aria-activedescendant', '');
- }
- };
- uiGridCtrl.focus = function () {
- gridUtil.focus.byElement(focuser[0]);
- //allow for first time grid focus
- };
- var viewPortKeyDownWasRaisedForRowCol = null;
- // Bind to keydown events in the render container
- focuser.on('keydown', function (evt) {
- evt.uiGridTargetRenderContainerId = containerId;
- var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
- var raiseViewPortKeyDown = uiGridCtrl.grid.options.keyDownOverrides.some(function (override) {
- return Object.keys(override).every( function (property) {
- return override[property] === evt[property];
- });
- });
- var result = raiseViewPortKeyDown ? null : uiGridCtrl.cellNav.handleKeyDown(evt);
- if (result === null) {
- uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol, uiGridCtrl.cellNav.handleKeyDown);
- viewPortKeyDownWasRaisedForRowCol = rowCol;
- }
- });
- //Bind to keypress events in the render container
- //keypress events are needed by edit function so the key press
- //that initiated an edit is not lost
- //must fire the event in a timeout so the editor can
- //initialize and subscribe to the event on another event loop
- focuser.on('keypress', function (evt) {
- if (viewPortKeyDownWasRaisedForRowCol) {
- $timeout(function () {
- uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
- },4);
- viewPortKeyDownWasRaisedForRowCol = null;
- }
- });
- $scope.$on('$destroy', function(){
- //Remove all event handlers associated with this focuser.
- focuser.off();
- });
- }
- };
- }
- };
- }]);
- module.directive('uiGridViewport', ['$timeout', '$document', 'gridUtil', 'uiGridConstants', 'uiGridCellNavService', 'uiGridCellNavConstants','$log','$compile',
- function ($timeout, $document, gridUtil, uiGridConstants, uiGridCellNavService, uiGridCellNavConstants, $log, $compile) {
- return {
- replace: true,
- priority: -99999, //this needs to run very last
- require: ['^uiGrid', '^uiGridRenderContainer', '?^uiGridCellnav'],
- scope: false,
- compile: function () {
- return {
- pre: function ($scope, $elm, $attrs, uiGridCtrl) {
- },
- post: function ($scope, $elm, $attrs, controllers) {
- var uiGridCtrl = controllers[0],
- renderContainerCtrl = controllers[1];
- // Skip attaching cell-nav specific logic if the directive is not attached above us
- if (!uiGridCtrl.grid.api.cellNav) { return; }
- var containerId = renderContainerCtrl.containerId;
- //no need to process for other containers
- if (containerId !== 'body') {
- return;
- }
- var grid = uiGridCtrl.grid;
- grid.api.core.on.scrollBegin($scope, function (args) {
- // Skip if there's no currently-focused cell
- var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
- if (lastRowCol === null) {
- return;
- }
- //if not in my container, move on
- //todo: worry about horiz scroll
- if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
- return;
- }
- uiGridCtrl.cellNav.clearFocus();
- });
- grid.api.core.on.scrollEnd($scope, function (args) {
- // Skip if there's no currently-focused cell
- var lastRowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
- if (lastRowCol === null) {
- return;
- }
- //if not in my container, move on
- //todo: worry about horiz scroll
- if (!renderContainerCtrl.colContainer.containsColumn(lastRowCol.col)) {
- return;
- }
- uiGridCtrl.cellNav.broadcastCellNav(lastRowCol);
- });
- grid.api.cellNav.on.navigate($scope, function () {
- //focus again because it can be lost
- uiGridCtrl.focus();
- });
- }
- };
- }
- };
- }]);
- /**
- * @ngdoc directive
- * @name ui.grid.cellNav.directive:uiGridCell
- * @element div
- * @restrict A
- * @description Stacks on top of ui.grid.uiGridCell to provide cell navigation
- */
- module.directive('uiGridCell', ['$timeout', '$document', 'uiGridCellNavService', 'gridUtil', 'uiGridCellNavConstants', 'uiGridConstants', 'GridRowColumn',
- function ($timeout, $document, uiGridCellNavService, gridUtil, uiGridCellNavConstants, uiGridConstants, GridRowColumn) {
- return {
- priority: -150, // run after default uiGridCell directive and ui.grid.edit uiGridCell
- restrict: 'A',
- require: ['^uiGrid', '?^uiGridCellnav'],
- scope: false,
- link: function ($scope, $elm, $attrs, controllers) {
- var uiGridCtrl = controllers[0],
- uiGridCellnavCtrl = controllers[1];
- // Skip attaching cell-nav specific logic if the directive is not attached above us
- if (!uiGridCtrl.grid.api.cellNav) { return; }
- if (!$scope.col.colDef.allowCellFocus) {
- return;
- }
- //Convinience local variables
- var grid = uiGridCtrl.grid;
- $scope.focused = false;
- // Make this cell focusable but only with javascript/a mouse click
- $elm.attr('tabindex', -1);
- // When a cell is clicked, broadcast a cellNav event saying that this row+col combo is now focused
- $elm.find('div').on('click', function (evt) {
- uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), evt.ctrlKey || evt.metaKey, evt);
- evt.stopPropagation();
- $scope.$apply();
- });
- /*
- * XXX Hack for screen readers.
- * This allows the grid to focus using only the screen reader cursor.
- * Since the focus event doesn't include key press information we can't use it
- * as our primary source of the event.
- */
- $elm.on('mousedown', preventMouseDown);
- //turn on and off for edit events
- if (uiGridCtrl.grid.api.edit) {
- uiGridCtrl.grid.api.edit.on.beginCellEdit($scope, function () {
- $elm.off('mousedown', preventMouseDown);
- });
- uiGridCtrl.grid.api.edit.on.afterCellEdit($scope, function () {
- $elm.on('mousedown', preventMouseDown);
- });
- uiGridCtrl.grid.api.edit.on.cancelCellEdit($scope, function () {
- $elm.on('mousedown', preventMouseDown);
- });
- }
- // In case we created a new row, and we are the new created row by ngRepeat
- // then this cell content might have been selected previously
- refreshCellFocus();
- function preventMouseDown(evt) {
- //Prevents the foucus event from firing if the click event is already going to fire.
- //If both events fire it will cause bouncing behavior.
- evt.preventDefault();
- }
- //You can only focus on elements with a tabindex value
- $elm.on('focus', function (evt) {
- uiGridCtrl.cellNav.broadcastCellNav(new GridRowColumn($scope.row, $scope.col), false, evt);
- evt.stopPropagation();
- $scope.$apply();
- });
- // This event is fired for all cells. If the cell matches, then focus is set
- $scope.$on(uiGridCellNavConstants.CELL_NAV_EVENT, refreshCellFocus);
- // Refresh cell focus when a new row id added to the grid
- var dataChangeDereg = uiGridCtrl.grid.registerDataChangeCallback(function (grid) {
- // Clear the focus if it's set to avoid the wrong cell getting focused during
- // a short period of time (from now until $timeout function executed)
- clearFocus();
- $scope.$applyAsync(refreshCellFocus);
- }, [uiGridConstants.dataChange.ROW]);
- function refreshCellFocus() {
- var isFocused = grid.cellNav.focusedCells.some(function (focusedRowCol, index) {
- return (focusedRowCol.row === $scope.row && focusedRowCol.col === $scope.col);
- });
- if (isFocused) {
- setFocused();
- } else {
- clearFocus();
- }
- }
- function setFocused() {
- if (!$scope.focused){
- var div = $elm.find('div');
- div.addClass('ui-grid-cell-focus');
- $elm.attr('aria-selected', true);
- uiGridCellnavCtrl.setAriaActivedescendant($elm.attr('id'));
- $scope.focused = true;
- }
- }
- function clearFocus() {
- if ($scope.focused){
- var div = $elm.find('div');
- div.removeClass('ui-grid-cell-focus');
- $elm.attr('aria-selected', false);
- uiGridCellnavCtrl.removeAriaActivedescendant($elm.attr('id'));
- $scope.focused = false;
- }
- }
- $scope.$on('$destroy', function () {
- dataChangeDereg();
- //.off withouth paramaters removes all handlers
- $elm.find('div').off();
- $elm.off();
- });
- }
- };
- }]);
- })();
|