I am working on angular project and came to a situation where i have series of div in a row and when user clicks on any div he should be able to move from one div to another using arrow key.
Please help.
I tried using keypress event but it didn't helped me. Tried finding out similar questions on stackoverflow but all answers where into jquery and i need it in in typescript.
moveCell(e){
console.log(e);
}
.container{
width: 100%;
}
.cell{
width: 100px;
float:left;
}
.cell:hover,
.cell:focus{
background: red;
}
<div class="container">
<div class="cell" (keypress)="moveCell($event)">cell 1</div>
<div class="cell" (keypress)="moveCell($event)">cell 2</div>
<div class="cell" (keypress)="moveCell($event)">cell 3</div>
<div class="cell" (keypress)="moveCell($event)">cell 4</div>
<div class="cell" (keypress)="moveCell($event)">cell 5</div>
<div class="cell" (keypress)="moveCell($event)">cell 6</div>
<div class="cell" (keypress)="moveCell($event)">cell 7</div>
<div class="cell" (keypress)="moveCell($event)">cell 8</div>
<div class="cell" (keypress)="moveCell($event)">cell 9</div>
</div>
In my code If user clicks on cell 2, focus should automatically go to cell 2. After that If user use keyboard and press arrow keys it should move focus to next/prev cell.
keypress
does not detect the arrow keys
, instead use keydown
. In order to receive the focus and listen to the key events add attribute tabindex
to the divs.
For right arrow key, you need to check if current active element is not a last element, and change the focus to the next element.
For left arrow key, check whether current active element is not a first element and then change the focus to the previous element.
--HTML--
<div class="container">
<div tabindex="0" class="cell" (keydown)="moveCell($event)">cell 2</div>
<div tabindex="1" class="cell" (keydown)="moveCell($event)">cell 3</div>
<div tabindex="2" class="cell" (keydown)="moveCell($event)">cell 4</div>
<div tabindex="3" class="cell" (keydown)="moveCell($event)">cell 5</div>
<div tabindex="4" class="cell" (keydown)="moveCell($event)">cell 6</div>
<div tabindex="5" class="cell" (keydown)="moveCell($event)">cell 7</div>
<div tabindex="6" class="cell" (keydown)="moveCell($event)">cell 8</div>
</div>
--Component code--
length: 0;
domEles;
moveCell(e){
const activeEle = document.activeElement;
const activeEleIndex = Array.prototype.indexOf.call(this.domEles, activeEle);
if(e.key == "ArrowRight" && activeEleIndex < this.length - 1 ) {
activeEle.nextElementSibling.focus();
}
if(e.key == "ArrowLeft" && activeEleIndex > 0) {
activeEle.previousElementSibling.focus();
}
}
ngOnInit() {
this.domEles = document.querySelectorAll('.container > *');
this.length = this.domEles.length;
}
Working Code
- https://stackblitz.com/edit/angular-5qxicw.
There're another aproach using directive. Well, the idea is that we get all the div that has our directive in our app.component using ViewChildren, then our div with a directive send an event and call a function of our app.component. So the app.component becomes like
<div arrow-div (event)="handler($event)>my div</div>
<div arrow-div (event)="handler($event)>my div</div>
...
But we can use a "service" to make the things more "transparents".
Imagine a service like
@Injectable({
providedIn: 'root',
})
export class KeyBoardService {
keyBoard:Subject<any>=new Subject<any>();
sendMessage(message:any)
{
this.keyBoard.next(message)
}
}
Our directive can call to the service "sendMessage" when a key arrow is pressed, and in our app.component subscribe to this service. and then our app.component is some like
<div arrow-div >my div</div>
<div arrow-div >my div</div>
<br/>
<div arrow-div >my div</div>
<div arrow-div >my div</div>
We avoid this "ugly" (event)="handler($event)" in our divs!!
Well, the directive in simple, using @Hostlistener to listen the key, and renderer2 to add the attributte "tabindex" (to make a div focusable, we need add tabIndex). So
@Directive({
selector: '[arrow-div]',
})
export class ArrowDivDirective {
constructor(private keyboardService: KeyBoardService, public element: ElementRef, private render: Renderer2) {
this.render.setAttribute(this.element.nativeElement, "tabindex", "0")
}
@HostListener('keydown', ['$event']) onKeyUp(e) {
switch (e.keyCode) {
case 38:
this.keyboardService.sendMessage({ element: this.element, action: 'UP' })
break;
case 37:
this.keyboardService.sendMessage({ element: this.element, action: 'LEFT' })
break;
case 40:
this.keyboardService.sendMessage({ element: this.element, action: 'DOWN' })
break;
case 39:
this.keyboardService.sendMessage({ element: this.element, action: 'RIGTH' })
break;
}
}
}
And our app.component.ts
export class AppComponent implements OnInit {
columns:number=2;
@ViewChildren(ArrowDivDirective) inputs:QueryList<ArrowDivDirective>
constructor(private keyboardService:KeyBoardService){}
ngOnInit()
{
this.keyboardService.keyBoard.subscribe(res=>{
this.move(res)
})
}
move(object)
{
const inputToArray=this.inputs.toArray()
let index=inputToArray.findIndex(x=>x.element==object.element);
switch (object.action)
{
case "UP":
index-=this.columns;
break;
case "DOWN":
index+=this.columns;
break;
case "LEFT":
index--;
break;
case "RIGTH":
index++;
break;
case "RIGTH":
index++;
break;
}
if (index>=0 && index<this.inputs.length)
inputToArray[index].element.nativeElement.focus();
}
}
See that I used a variable "column" if we are making a "grid" with cols and rows and use the up and down keys to move between rows. Sending the "element" avoid we must have stored the "div focused"
You can see an example in stackblitz
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With