I stole Gunter's Plunker example, just because I'm lazy, and changed and extended the it's functionality .
Although mine is completely different than Gunter's one.
The problem with the directive that Gunter has written is that he hasn't separated the directive and the component concerns completely.
I can see that he's calling focus function of the MyFocus directive from the component class, which is not a good idea , because practically a component should not be aware of the insides methods of a directive .
I've also added up and down and enter so you can fully enjoy a free fantastic directive :)
There you go : Focuser Directive in Plunker
import {Component, ElementRef, Directive, ViewChildren, Renderer, QueryList} from '@angular/core'
import {EventEmitter , Input , AfterViewInit } from '@angular/core';
import { BrowserDomAdapter } from "@angular/platform-browser/src/browser/browser_adapter";
@Directive( {
selector : '[focuser]' ,
host : {
'(keydown)' : 'onKeydown($event)' ,
'(keydown.enter)' : 'onEnterPressed()'
} ,
outputs : [ 'enterpress' , 'focusOut' ]
} )
export class FocuserDirective implements AfterViewInit {
@Input( 'focuser' ) parentEvent : EventEmitter<string>;
@Input( 'hasList' ) hasList : boolean;
private lastTabindex = -1;
private enterpress = new EventEmitter<number>();
private focusOut = new EventEmitter<string>();
private listElements;
private liScrolHeight = 0;
private domAdapter : BrowserDomAdapter;
ngAfterViewInit () : any {
this.parentEvent.subscribe( ()=> {
this._renderer.invokeElementMethod( this._el.nativeElement , 'focus' , [] );
if ( this.hasList ) {
this.listElements = this.domAdapter.querySelectorAll( this._el.nativeElement , 'li' );
if(this.listElements){
this.liScrolHeight = this.domAdapter.getProperty( this.listElements[ 0 ] , 'scrollHeight' );
this.next();
}
}
} );
return undefined;
}
private onKeydown ( $event ) {
if ( !this.hasList ) {
return;
}
let keyCode = $event.keyCode;
if ( keyCode === KeyCodes.DOWN ) {
this.next();
} else if ( keyCode === KeyCodes.UP ) {
this.prev();
} else {
$event.preventDefault();
}
}
private onEnterPressed () {
if ( !this.hasList ) {
return;
}
this.enterpress.emit( this.lastTabindex );
}
constructor ( private _el : ElementRef , private _renderer : Renderer ) {
this.domAdapter = new BrowserDomAdapter();
}
private prev () {
this.lastTabindex--;
if ( this.lastTabindex === -1 ) {
this.onFocusOut();
return;
}
this.setScrollTopAndFocus();
}
private next () {
if ( this.lastTabindex === this.listElements.length - 1 ) {
return;
}
this.lastTabindex++;
this.setScrollTopAndFocus();
}
private setScrollTopAndFocus () {
this._renderer.setElementProperty( this._el.nativeElement , 'scrollTop' , this.lastTabindex * this.liScrolHeight );
this._renderer.invokeElementMethod( this.listElements[ this.lastTabindex ] , 'focus' , [] );
}
private onFocusOut () {
this.focusOut.emit( 'focus out' );
}
}
@Component({
selector: 'my-app',
providers: [],
styles: [`li:focus { background-color: yellow;}`],
template: `
<input [focuser]='$focusInput' tabindex="-1" type="text" (keyup)="onSearch($event)">
<focuser [focuser]="$focus" hasList='true' (focusOut)="onListFocusOut()" (enterpress)='onSelect($event)'>
<br>
Try pressing down or up or enter !!!!!!!!!
<br>
<ul tabindex="0">
<li [tabindex]="i" *ngFor='let item of results ; let i = index'>{{item}}</li>
</ul>
selected :
<ul >
<li *ngFor='let item of selected'>{{item}}</li>
</ul>
</focuser>
`,
directives: [FocuserDirective]
})
export class App {
private $focus = new EventEmitter<string>()
private $focusInput = new EventEmitter<string>()
private selected = [];
results = ['Iran is beautiful', 'Angular2 is great', 'We love you all ']
constructor() {
this.name = 'Angular2 (Release Candidate!)'
}
onSearch($event) {
console.log('$event',$event.keyCode);
if($event.keyCode===40){
this.$focus.emit('Please focus mr directive !');
}
}
private onListFocusOut(){
this.$focusInput.emit('please focus on my input');
}
private onSelect(index){
this.selected.push(this.results[index])
}
}
export abstract class KeyCodes {
static LEFT = 37;
static UP = 38;
static RIGHT = 39;
static DOWN = 40;
static BACKSPACE = 8;
static ENTER = 13;
static ARROWS = [ 37 , 38 , 39 , 40 ];
}