I have written a table and in it all contain input fields. For this template I have developed a keyCode navigation. A navigation up, down, left and right is possible. What do I have to add to my code so that the cursor is focused directly in the when navigating?
My Code:
// Snippet from HTML
...
<tbody formArrayName="rows">
<tr *ngFor="let rowControl of rows.controls; let rowIndex = index">
<td [formGroupName]="rowIndex" appArrowKeyNav *ngFor="let column of displayedColumns; let columnIndex = index;">
<div>
<span>
<label>
<input [formControl]="rowControl.get(column.attribute)">
</label>
</span>
</div>
</td>
</tr>
</tbody>
// TS
public move(object: any) {
const inputToArray = this.inputs.toArray();
const cols = this.displayedColumns.length;
let index = inputToArray.findIndex((x) => x.element === object.element);
// console.log('Index found:', index);
switch (object.action) {
case 'UP':
index -= cols;
break;
case 'DOWN':
index += cols;
break;
case 'LEFT':
index -= 1;
break;
case 'RIGHT':
index += 1;
break;
}
if (index >= 0 && index < this.inputs.length) {
console.log('Navigating to index:', index);
inputToArray[index].element.nativeElement.focus();
}
}
// Directive
@HostListener('keydown', ['$event']) onKeyUp(event: KeyboardEvent) {
switch (event.key) {
case 'ArrowUp':
this.keyboardService.sendMessage({ element: this.element, action: 'UP' });
break;
case 'ArrowLeft':
this.keyboardService.sendMessage({ element: this.element, action: 'LEFT' });
break;
case 'ArrowDown':
this.keyboardService.sendMessage({ element: this.element, action: 'DOWN' });
break;
case 'ArrowRight':
this.keyboardService.sendMessage({ element: this.element, action: 'RIGHT' });
break;
case 'Enter':
this.keyboardService.sendMessage({ element: this.element, action: 'ENTER' });
break;
}
}
Here is myStackblitz: https://stackblitz.com/edit/angular-wmfjhh-zfkyyx?file=app%2Ftable-basic-example.html
One possible way to have a table with cells that can be navigated with Arrow keys
use the id
attribute to store row and col information using the *ngFor
index
<tr *ngFor="let rowControl of rows.controls; let i = index">
<ng-container [formGroupName]="i">
<td>
<input [id]="'row-' + i + '-col-0'" formControlName="name" (focus)="onFocus($event)">
</td>
<td>
<input [id]="'row-' + i + '-col-1'" formControlName="age" (focus)="onFocus($event)">
</td>
<td>
<input [id]="'row-' + i + '-col-2'" formControlName="color" (focus)="onFocus($event)">
</td>
</ng-container>
</tr>
in this case id for first row will be row-0-col-0
, row-0-col-1
etc.
2nd row row-1-col-0
also there is a onFocus
event handler which will set the current cell in focus
once keyUp
is triggered
@HostListener('keydown', ['$event'])
onKeyUp(event: KeyboardEvent) {
console.log(event.key);
if (this.currentInputInFocus) {
const id = this.currentInputInFocus.id;
if (id && id.includes('row-')) {
const arr = id.split('-');
let row: number = Number(arr[1]);
let col: number = Number(arr[3]);
switch (event.key) {
case 'ArrowUp':
--row;
break;
case 'ArrowLeft':
--col;
break;
case 'ArrowDown':
++row;
break;
case 'ArrowRight':
++col;
break;
case 'Enter':
// do nothing
break;
}
this.setFocus(row, col);
}
}
}
get the currently focused element id, eg. 'row-0-col-0' and make changes to row or col values depending on a keypress then try to focus the new element
private setFocus(row: number, col: number) {
const newElementToFocusOn = document.getElementById(`row-${row}-col-${col}`);
if (newElementToFocusOn) {
this.currentInputInFocus = newElementToFocusOn;
this.currentInputInFocus.focus();
}
}
in ngAfterViewInit
initially focused cell can be set with:
ngAfterViewInit() {
const row = 1;
const col = 0;
this.setFocus(row, col);
}
UPDATE
to use your Stackblitz as context very few mods to make it work:
[id]="'row-' + rowIndex + '-col-' + columnIndex"
currentInputInFocus!: HTMLElement;
Forked and updated your Stackblitz
UPDATE 2nd
The HTMLElement.focus() method sets focus on the specified element, if it can be focused.
One workaround is to add the tabindex="0"
attribute according to this answer
Back to demo. As @Aviad P. mentioned the directive on ng-template
is not going to work. Instead:
<ng-template #otherColumns>
<div tabindex="0" [id]="'row-' + rowIndex + '-col-' + columnIndex" arrow-div>
Here is a Number
</div>
</ng-template>
UPDATE 3rd
to continue navigating you can try this on case 'RIGTH':
++col; // try to move to next column
if (col >= this.columns.length) { // check if moved past last column
col = 0; // go to column at zero index (1st column)
++row; // go to next row
}