Skip to content

Commit e9a6d4e

Browse files
committed
fix(edit): fix lost focus and cell scrolling into view on edit
1 parent 3253caf commit e9a6d4e

File tree

4 files changed

+89
-22
lines changed

4 files changed

+89
-22
lines changed

src/features/cellnav/js/cellnav.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,20 @@
309309
* @param {object} event keydown event
310310
* @param {object} rowCol current rowCol position
311311
*/
312-
viewPortKeyDown: function (event, rowCol) {}
312+
viewPortKeyDown: function (event, rowCol) {},
313+
314+
/**
315+
* @ngdoc event
316+
* @name viewPortKeyPress
317+
* @eventOf ui.grid.cellNav.api:PublicApi
318+
* @description is raised when the viewPort receives a keyPress event. Cells never get focus in uiGrid
319+
* due to the difficulties of setting focus on a cell that is not visible in the viewport. Use this
320+
* event whenever you need a keypress event on a cell
321+
* <br/>
322+
* @param {object} event keypress event
323+
* @param {object} rowCol current rowCol position
324+
*/
325+
viewPortKeyPress: function (event, rowCol) {}
313326
}
314327
},
315328
methods: {
@@ -775,15 +788,29 @@
775788
});
776789
};
777790

778-
779-
791+
var viewPortKeyDownWasRaisedForRowCol = null;
780792
// Bind to keydown events in the render container
781793
focuser.on('keydown', function (evt) {
782794
evt.uiGridTargetRenderContainerId = containerId;
783795
var rowCol = uiGridCtrl.grid.api.cellNav.getFocusedCell();
784796
var result = uiGridCtrl.cellNav.handleKeyDown(evt);
785797
if (result === null) {
786798
uiGridCtrl.grid.api.cellNav.raise.viewPortKeyDown(evt, rowCol);
799+
viewPortKeyDownWasRaisedForRowCol = rowCol;
800+
}
801+
});
802+
//Bind to keypress events in the render container
803+
//keypress events are needed by edit function so the key press
804+
//that initiated an edit is not lost
805+
//must fire the event in a timeout so the editor can
806+
//initialize and subscribe to the event on another event loop
807+
focuser.on('keypress', function (evt) {
808+
if (viewPortKeyDownWasRaisedForRowCol) {
809+
$timeout(function () {
810+
uiGridCtrl.grid.api.cellNav.raise.viewPortKeyPress(evt, viewPortKeyDownWasRaisedForRowCol);
811+
},4);
812+
813+
viewPortKeyDownWasRaisedForRowCol = null;
787814
}
788815
});
789816

src/features/edit/js/gridEdit.js

+44-14
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@
492492
if (rowCol.row === $scope.row && rowCol.col === $scope.col && !$scope.col.colDef.enableCellEditOnFocus) {
493493
//important to do this before scrollToIfNecessary
494494
beginEditKeyDown(evt);
495-
uiGridCtrl.grid.api.core.scrollToIfNecessary(rowCol.row, rowCol.col);
495+
// uiGridCtrl.grid.api.core.scrollToIfNecessary(rowCol.row, rowCol.col);
496496
}
497497

498498
});
@@ -582,6 +582,14 @@
582582
}
583583

584584

585+
function beginEdit(triggerEvent) {
586+
//we need to scroll the cell into focus before invoking the editor
587+
$scope.grid.api.core.scrollToIfNecessary($scope.row, $scope.col)
588+
.then(function () {
589+
beginEditAfterScroll(triggerEvent);
590+
});
591+
}
592+
585593
/**
586594
* @ngdoc property
587595
* @name editDropdownOptionsArray
@@ -666,7 +674,7 @@
666674
* </pre>
667675
*
668676
*/
669-
function beginEdit(triggerEvent) {
677+
function beginEditAfterScroll(triggerEvent) {
670678
// If we are already editing, then just skip this so we don't try editing twice...
671679
if (inEdit) {
672680
return;
@@ -738,16 +746,16 @@
738746

739747
//stop editing when grid is scrolled
740748
var deregOnGridScroll = $scope.col.grid.api.core.on.scrollBegin($scope, function () {
741-
endEdit(true);
749+
endEdit();
742750
$scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
743751
deregOnGridScroll();
744752
deregOnEndCellEdit();
745753
deregOnCancelCellEdit();
746754
});
747755

748756
//end editing
749-
var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function (evt, retainFocus) {
750-
endEdit(retainFocus);
757+
var deregOnEndCellEdit = $scope.$on(uiGridEditConstants.events.END_CELL_EDIT, function () {
758+
endEdit();
751759
$scope.grid.api.edit.raise.afterCellEdit($scope.row.entity, $scope.col.colDef, cellModel($scope), origCellValue);
752760
deregOnEndCellEdit();
753761
deregOnGridScroll();
@@ -766,7 +774,7 @@
766774
$scope.grid.api.edit.raise.beginCellEdit($scope.row.entity, $scope.col.colDef, triggerEvent);
767775
}
768776

769-
function endEdit(retainFocus) {
777+
function endEdit() {
770778
$scope.grid.disableScrolling = false;
771779
if (!inEdit) {
772780
return;
@@ -779,6 +787,11 @@
779787
inEdit = false;
780788
registerBeginEditEvents();
781789
$scope.grid.api.core.notifyDataChange( uiGridConstants.dataChange.EDIT );
790+
//sometimes the events can't keep up with the keyboard and grid focus is lost, so always focus
791+
//back to grid here
792+
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
793+
uiGridCtrl.focus();
794+
}
782795
}
783796

784797
function cancelEdit() {
@@ -790,7 +803,7 @@
790803
$scope.$apply();
791804

792805
$scope.grid.api.edit.raise.cancelCellEdit($scope.row.entity, $scope.col.colDef);
793-
endEdit(true);
806+
endEdit();
794807
}
795808

796809
// resolves a string path against the given object
@@ -832,25 +845,42 @@
832845
*
833846
*/
834847
module.directive('uiGridEditor',
835-
['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout',
836-
function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout) {
848+
['gridUtil', 'uiGridConstants', 'uiGridEditConstants','$timeout', 'uiGridEditService',
849+
function (gridUtil, uiGridConstants, uiGridEditConstants, $timeout, uiGridEditService) {
837850
return {
838851
scope: true,
839-
require: ['?^uiGrid', '?^uiGridRenderContainer'],
852+
require: ['?^uiGrid', '?^uiGridRenderContainer', 'ngModel'],
840853
compile: function () {
841854
return {
842855
pre: function ($scope, $elm, $attrs) {
843856

844857
},
845858
post: function ($scope, $elm, $attrs, controllers) {
846-
var uiGridCtrl, renderContainerCtrl;
859+
var uiGridCtrl, renderContainerCtrl, ngModel;
847860
if (controllers[0]) { uiGridCtrl = controllers[0]; }
848861
if (controllers[1]) { renderContainerCtrl = controllers[1]; }
862+
if (controllers[2]) { ngModel = controllers[2]; }
849863

850864
//set focus at start of edit
851-
$scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function () {
852-
$elm[0].focus();
853-
$elm[0].select();
865+
$scope.$on(uiGridEditConstants.events.BEGIN_CELL_EDIT, function (evt,triggerEvent) {
866+
$timeout(function () {
867+
$elm[0].focus();
868+
$elm[0].select();
869+
});
870+
871+
//set the keystroke that started the edit event
872+
//we must do this because the BeginEdit is done in a different event loop than the intitial
873+
//keydown event
874+
//fire this event for the keypress that is received
875+
if (uiGridCtrl && uiGridCtrl.grid.api.cellNav) {
876+
var viewPortKeyDownUnregister = uiGridCtrl.grid.api.cellNav.on.viewPortKeyPress($scope, function (evt, rowCol) {
877+
if (uiGridEditService.isStartEditKey(evt)) {
878+
ngModel.$setViewValue(String.fromCharCode(evt.keyCode), evt);
879+
ngModel.$render();
880+
}
881+
viewPortKeyDownUnregister();
882+
});
883+
}
854884

855885
$elm.on('blur', function (evt) {
856886
$scope.stopEdit(evt);

src/features/edit/test/uiGridCell.spec.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,11 @@ describe('ui.grid.edit GridCellDirective', function () {
8383
expect(element.text()).toBe('val');
8484
//invoke edit
8585
element.dblclick();
86-
expect(element.find('input')).toBeDefined();
87-
expect(element.find('input').val()).toBe('val');
86+
$timeout(function () {
87+
expect(element.find('input')).toBeDefined();
88+
expect(element.find('input').val()).toBe('val');
89+
});
90+
$timeout.flush();
8891
});
8992

9093
it('should stop editing on enter', function () {

src/features/edit/test/uiGridCellWithDropdown.spec.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ describe('ui.grid.edit GridCellDirective - with dropdown', function () {
8585
element.dblclick();
8686
expect(element.find('select')).toBeDefined();
8787

88-
// val is the selected option, which is option 0
89-
expect(element.find('select').val()).toBe('0');
88+
$timeout(function () {
89+
// val is the selected option, which is option 0
90+
expect(element.find('select').val()).toBe('0');
91+
});
92+
$timeout.flush();
9093
});
9194

9295
it('should stop editing on enter', function () {
@@ -96,7 +99,11 @@ describe('ui.grid.edit GridCellDirective - with dropdown', function () {
9699
element.find('select').trigger(event);
97100

98101
//back to beginning
99-
expect(element.html()).toBe(displayHtml);
102+
$timeout(function () {
103+
expect(element.html()).toBe(displayHtml);
104+
});
105+
$timeout.flush();
106+
100107
});
101108

102109
it('should stop editing on esc', function () {

0 commit comments

Comments
 (0)