xero xero - 6 months ago 50
HTML Question

input with custom search result , how to change focus to jump over the results

I'm making a custom search input like bellow

<input type="text" (keyup)="search()">

<ul>
<li *ngFor='let item of results'>{{item}}</li> // Angular2 syntax for repeating over my array
</ul>


search(){
//do the search :

// then attach the resuls

this.results = apiResponse;

}


This is working and all good.

Now I'm wondering how I can implement the functionality where if user is focused in the input and there are some results bellow the input ( in the
ul
) , then if he presses the down arrow key in the keyboard , I would want to change the focus of the input and jump over the result so user can press enter and pick on of them.

Like google or all the other search inputs do .

Forgive me if I can't explain it correctly , it's hard to explain though.

Thanks in advance.

EDIT :

After some google , realized I can capture the keydown , keycode and if the keycode is downkey , I can focus on the first li element , BUT , this is not working , even after setting the tabindex.

<input tabindex="-1" type="text" (keyup)="search($event)">

<ul tabindex="0">
<li tabindex="{{i}}" *ngFor='let item of results ; let i = index'>{{item}}</li> // Angular2 syntax for repeating over my array
</ul>


search($event){
//do the search :

// then attach the results;

if($event.keykode===40)// on down key
{
let li = this._el.nativeElement.querySelectorAll('li')[0];
// I've got the li here , it's good
li.focus(); ----->>>>>>>> not working !!!!
// we don't want to trigger api search any more , so returning
return ;
}
this.results = apiResponse;

}





Even if I inspect the li element via chrome dev tools and do
$0.focus()
, nothing happens .!!

EDIT :

Adding screenshot of the list :
enter image description here

This is a long list , it starts from 1 , not 11 (removed couple of lis to make the screenshot smaller).

Answer

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 ];
}