Reputation: 589
I would like to bind arrows keyup's events to jump to another input in a MatTable.
I would like to do something like the following situation:
mycomponent.html
<table mat-table [dataSource]="dataSource">
<ng-container matColumnDef="name">
<th th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *matCellDef="let element">
<input type="text" (keyup.arrowup)="jumpToPreviousNameInput()" (keyup.arrowdown)="jumpToNextNameInput()" (keyup.arrowright)="jumpToAgeInput()"/>
</td>
</ng-container>
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef>Age</th>
<td mat-cell *matCellDef="let element">
<input type="number" min="0" (keyup.arrowup)="jumpToPreviousAgeInput()" (keyup.arrowdown)="jumpToNextAgeInput()" (keyup.arrowleft)="jumpToNameInput()" />
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns; let element" class="table-row" </tr>
</table>
Is this possible with Angular 9?
I have no idea about how can I get the next input element in Angular. In Vanilla JS I would do something like:
let myTable = document.querySelector('table')
let inputs = myTable.querySelectorAll('input')
inputs.forEach((input, index) => {
input.addEventListener('keydown', e => {
const key = e.key;
switch(key) {
case "ArrowLeft":
try { inputs[index-1].focus() } catch {}
break;
case "ArrowRight":
try { inputs[index + 1].focus() } catch { }
break;
case "ArrowUp":
try { inputs[index - 2].focus() } catch { }
break;
case "ArrowDown":
try { inputs[index + 2].focus() } catch { }
break;
}
})
})
Upvotes: 0
Views: 1169
Reputation: 4032
This can be easily done with ListKeyManager
from @angular/cdk. @angular/cdk has lots of core basic features. Here i am sharing a bare simple code. You will have to make changes to it accordingly.
import { Component, ViewChild, ViewChildren, ElementRef, QueryList, HostListener, AfterViewInit } from '@angular/core';
import { FocusTrapFactory, FocusMonitor, ListKeyManager} from '@angular/cdk/a11y'
@Component({
selector: 'app-root',
template: `
<button (click)="testA11y()"> Test A11y! </button>
<div #element role="dialog" hidden=true>
<label>Sample field</label>
<input #elementChild>
<label>Sample field</label>
<input #elementChild>
<label>Sample field</label>
<input #elementChild>
<label>Sample field</label>
<input #elementChild>
<label>Sample field</label>
<input #elementChild>
</div>`
})
export class AppComponent implements AfterViewInit {
keyManager : any
@ViewChild('element') element : ElementRef;
@ViewChildren('elementChild') elementChild : QueryList<any>
constructor( private focusTrap: FocusTrapFactory,
private focusMonitor : FocusMonitor ) {}
ngAfterViewInit() {
this.keyManager = new ListKeyManager(this.elementChild)
this.keyManager.withHorizontalOrientation('ltr') // Arrow navigation options
this.keyManager.withWrap() // Arrow navigation options
}
/* Enables keyboard arrows navigation */
@HostListener('window:keyup', ['$event'])
keyFunc(event) {
if (event.code !== 'Tab') {
this.keyManager.onKeydown(event)
this.focusMonitor.focusVia(this.keyManager.activeItem.nativeElement, "keyboard")
}
else { // 'artificially' updates the active element in case the user uses Tab instead of arrows
this.keyManager.onKeydown(event)
this.keyManager.setNextItemActive()
}
}
/* Shows the form, puts focus on it and initialize keyboard navigation */
testA11y() {
this.element.nativeElement.hidden = false;
let focusTrap = this.focusTrap.create(this.element.nativeElement) // creates a focus trap region
focusTrap.focusInitialElement() // Moves the focus in the form (by default the first field)
this.keyManager.setFirstItemActive() // Sets the element we focused on as 'active' to the KeyManager
}
}
There are also some good articles on it https://moduscreate.com/blog/adding-keyboard-navigation-to-angular-lists-using-angular-cdk-listkeymanager/ (NOTE: -not written by me)
Upvotes: 1