import { Component , Input , Output , EventEmitter , AfterContentInit , OnChanges , Renderer2 , ElementRef , forwardRef , OnDestroy , ViewChild , Inject } from "@angular/core" ; import { NG_VALUE_ACCESSOR , NG_VALIDATORS , FormControl } from "@angular/forms" ; import { MultiLingualData } from "@rxweb/localization" ; import { NgModule } from "@angular/core" ; import { CommonModule } from "@angular/common" ; import { FormsModule , ReactiveFormsModule } from "@angular/forms" ; import { RxHttp } from "@rxweb/http" ; export class SelectModel { constructor ( public key , public value , public active ) { } } @ Component ( { selector : 'rx-select' , templateUrl : "./rx-select.component.html" , providers : [ { provide : NG_VALUE_ACCESSOR , useExisting : forwardRef ( ( ) => RxSelectComponent ) , multi : true } , { provide : NG_VALIDATORS , useExisting : forwardRef ( ( ) => RxSelectComponent ) , multi : true } ] , exportAs : 'select' , } ) export class RxSelectComponent extends RxHttp implements AfterContentInit , OnDestroy , OnChanges { @ ViewChild ( "selectSearch" ) selectSearch ; private propagateChange : any = ( ) => { } ; element : Element ; private currentValue : string ; openDrop : string ; activeTag : string ; userSource : Array < any > ; public dropSource : Array < SelectModel > ; private mainSource : Array < SelectModel > ; private timeOutId : number ; private validTimeOutId : number ; private inputDisabled : boolean = false ; private currentFormControl : FormControl ; isInValid : boolean = false ; dropIndex : number = 0 ; isSpinner : boolean = false ; selectActive : boolean = false ; @ Output ( ) blur : EventEmitter < any > = new EventEmitter < any > ( ) ; @ Output ( ) changed : EventEmitter < any > = new EventEmitter < any > ( ) ; @ Input ( ) mainClass : string ; @ Input ( ) maxLength : number = 500 ; selectedTag : SelectModel ; @ Input ( ) selectPlaceholder : string = "" ; @ Input ( ) showSelectItem : boolean = true ; @ Input ( ) controlTabIndex : number ; selectConfiguration : { [ key ] : any ; } ; selectId : number ; constructor ( public elementRef , public renderer ) { super ( ) ; this . mainSource = new Array < SelectModel > ( ) ; this . dropSource = new Array < SelectModel > ( ) ; this . element = elementRef . nativeElement as Element ; let placeholderText = MultiLingualData . get ( "control.rx-select.placeholder" ) ; if ( this . selectPlaceholder == "" && placeholderText ) this . selectPlaceholder = this . selectConfiguration . placeholder ; } @ Input ( ) set source ( value ) { if ( value ) { this . userSource = value ; if ( this . keyValueProps ) this . setMainSource ( this . userSource ) ; } } @ Input ( ) disabled : boolean ; @ Input ( ) keyValueProps : Array < string > ; @ Input ( ) focus : boolean = false ; @ Input ( ) path : string ; @ Input ( ) params : string [ ] ; @ Input ( ) queryParams : { [ key ] : any ; } ; @ Input ( ) autoComplete : boolean = false ; @ Input ( ) minimumCharacterSearchLength : number ; @ Input ( ) enableFreeText : boolean ; @ Input ( ) set text ( value ) { if ( value ) { if ( this . selectSearch && this . selectSearch . nativeElement ) this . selectSearch . nativeElement . value = value ; this . controlText = value ; } } ; controlText : string ; isObjectChanged : boolean = false ; ngAfterContentInit ( ) { this . checkValid ( true ) ; if ( this . userSource && this . mainSource . length === 0 ) this . setMainSource ( this . userSource ) ; this . setValue ( ) ; if ( this . maxLength && this . selectSearch ) this . selectSearch . nativeElement . maxLength = this . maxLength ; } setValue ( isBlur = false , columnName = "key" ) { if ( ! this . autoComplete && this . currentValue && this . mainSource . length > 0 ) { var text = this . mainSource . filter ( t => String ( t [ columnName ] ) . toLowerCase ( ) == this . currentValue . toLowerCase ( ) ) [ 0 ] ; if ( text ) { this . writeValue ( text . value ) ; if ( isBlur ) { this . updateFormValue ( text . key ) ; this . selectSearch . nativeElement . value = text . value ; } return true ; } else { this . updateFormValue ( undefined ) ; } return false ; } return true ; } ngOnChanges ( source ) { if ( source . params || source . queryParams ) { this . isObjectChanged = true ; this . clear ( ) ; } } private checkValid ( isValid ) { if ( isValid ) this . validTimeOutId = window . setTimeout ( ( ) => { for ( var i = 0 ; i < this . element . classList . length ; i ++ ) { this . isInValid = this . element . classList [ i ] == "ng-invalid" ; } window . clearTimeout ( this . validTimeOutId ) ; this . validTimeOutId = undefined ; } , 500 ) ; else this . isInValid = isValid ; } private setMainSource ( source ) { this . mainSource = this . getTagModelSource ( source ) ; this . mainSource . forEach ( t => this . dropSource . push ( t ) ) ; this . clearActiveTabClass ( ) ; } private setDropSource ( source ) { this . dropSource = new Array < SelectModel > ( ) ; source . forEach ( t => this . dropSource . push ( t ) ) ; } private getTagModelSource ( source ) { var textPattern = this . keyValueProps [ 0 ] . split ( /#/g ) ; var tags = new Array < SelectModel > ( ) ; source . forEach ( t => { var text = '' ; textPattern . forEach ( x => { if ( x != "" ) if ( t [ x ] != undefined ) text += t [ x ] ; else text += x ; } ) ; tags . push ( new SelectModel ( t [ this . keyValueProps [ 1 ] ] , text , false ) ) ; } ) ; return tags ; } resetSource ( source ) { this . userSource = source ; this . mainSource = [ ] ; this . dropSource = [ ] ; this . setMainSource ( source ) ; this . currentValue = "" ; this . selectSearch . nativeElement . value = "" ; this . updateFormValue ( undefined ) ; } showItems ( tags , clearExisting ) { } clear ( event ? ) { this . selectSearch . nativeElement . value = '' ; this . onBlur ( event , this . selectSearch . nativeElement . value ) ; this . changed . emit ( { item : this . selectedTag , text : this . currentValue } ) ; } private registerOnChange ( fn ) { this . propagateChange = fn ; } private registerOnTouched ( ) { } private writeValue ( value ) { if ( value ) { this . currentValue = value ; if ( this . selectSearch && this . selectSearch . nativeElement ) if ( this . controlText ) this . selectSearch . nativeElement . value = this . controlText ; else this . selectSearch . nativeElement . value = value ; } else { this . currentValue = undefined ; if ( this . selectSearch && this . selectSearch . nativeElement ) this . selectSearch . nativeElement . value = "" ; } } caretClick ( event ) { if ( this . activeTag == undefined || this . activeTag == "" ) { var t = setTimeout ( ( ) => { this . selectSearch . nativeElement . focus ( ) ; this . onFocus ( event ) ; } , 200 ) ; } } onFocus ( event ) { if ( ( ! this . userSource && this . mainSource . length === 0 && this . lookup ) || ( this . autoComplete ) || this . isObjectChanged ) { if ( ( this . currentValue && this . currentValue . length >= this . minimumCharacterSearchLength ) || ( ! this . autoComplete ) ) { let params = [ ] ; if ( this . params ) this . params . forEach ( t => params . push ( t ) ) ; if ( this . autoComplete && this . currentValue ) params . push ( this . currentValue ) ; this . isSpinner = true ; this . get ( { path : this . path , params : params , queryParams : this . queryParams } ) . subscribe ( ( t ) => { this . userSource = t ; this . mainSource = [ ] ; this . dropSource = [ ] ; this . setMainSource ( t ) ; this . showItemBox ( ) ; this . isSpinner = false ; this . isObjectChanged = false ; } ) ; } } else if ( ! this . autoComplete ) this . showItemBox ( ) ; return false ; } showItemBox ( ) { this . setActiveTabClass ( ) ; var selectedItem = this . dropSource . filter ( t => t . value == this . currentValue ) [ 0 ] ; if ( selectedItem ) selectedItem . active = true ; } onBlur ( event , value ) { if ( this . timeOutId ) window . clearTimeout ( this . timeOutId ) ; this . timeOutId = window . setTimeout ( ( ) => { this . clearActiveTabClass ( ) ; } , 200 ) ; this . currentValue = value ; if ( ! this . setValue ( true , "value" ) || value == "" ) { if ( value != "" && ! this . enableFreeText ) { this . currentValue = "" ; this . selectSearch . nativeElement . value = "" ; this . updateFormValue ( undefined ) ; this . selectedTag = undefined ; this . blur . emit ( { item : this . selectedTag , text : this . currentValue } ) ; } else { this . updateFormValue ( value ) ; this . blur . emit ( { item : undefined , text : value } ) ; } } } onKeyup ( event , scroller ) { if ( event . key == "Tab" ) { this . onFocus ( event ) ; return ; } var value = ( < HTMLInputElement > event . target ) . value ; if ( value !== null && event . key !== "ArrowUp" && event . key !== "ArrowDown" && event . key !== "Enter" && event . key !== "ArrowLeft" && event . key !== "ArrowRight" ) { if ( this . autoComplete ) { this . currentValue = value ; this . onFocus ( event ) ; } else { var regex = new RegExp ( value . toLowerCase ( ) ) ; this . dropSource = this . mainSource . filter ( t => regex . test ( t . value . toLowerCase ( ) ) ) ; this . setActiveTabClass ( ) ; } if ( this . enableFreeText ) this . updateFormValue ( value ) ; } } onKeydown ( event , scroller ) { if ( event . key === "ArrowDown" ) { event . preventDefault ( ) ; if ( this . dropSource . length !== this . dropIndex ) { if ( this . dropIndex !== 0 ) { this . dropSource [ this . dropIndex - 1 ] . active = false ; this . dropSource [ this . dropIndex ] . active = true ; if ( this . dropIndex !== this . dropSource . length - 1 ) this . dropIndex ++ ; } else { this . dropSource [ this . dropIndex ] . active = true ; this . dropIndex ++ ; } } this . changeScroll ( ) ; if ( this . openDrop === "" ) this . setActiveTabClass ( ) ; } else if ( event . key === "ArrowUp" ) { event . preventDefault ( ) ; if ( ( this . dropSource . length - 1 ) === this . dropIndex ) { this . dropSource [ this . dropIndex ] . active = false ; this . dropIndex -- ; this . dropSource [ this . dropIndex ] . active = true ; } else { this . dropSource [ this . dropIndex ] . active = false ; if ( this . dropIndex !== 0 ) this . dropIndex -- ; this . dropSource [ this . dropIndex ] . active = true ; } this . changeScroll ( ) ; if ( this . openDrop === "" ) this . setActiveTabClass ( ) ; } else if ( event . key === "Enter" ) { event . preventDefault ( ) ; const selectedTag = this . dropSource . filter ( t => t . active ) ; if ( selectedTag . length > 0 ) { this . selectedTag = selectedTag [ 0 ] ; this . selectSearch . nativeElement . value = selectedTag [ 0 ] . value ; this . writeValue ( selectedTag [ 0 ] . value ) ; this . clearActiveTabClass ( ) ; } } } private setActiveTabClass ( ) { this . activeTag = "chosen-container-active" ; this . openDrop = "chosen-with-drop" ; } private changeScroll ( ) { var scrollerElement = document . getElementById ( String ( this . selectId ) ) ; let childElement = scrollerElement . getElementsByClassName ( "activeTag" ) [ 0 ] ; if ( childElement != undefined ) scrollerElement . scrollTop = childElement . offsetTop - 50 ; } onChange ( targetElement ) { let selectModel = this . dropSource [ targetElement . selectedIndex ] ; this . selectTag ( selectModel ) ; } enter ( targetElement ) { this . onClick ( this . selectedTag , targetElement ) ; } onClick ( tag , tagSearchElement ) { this . selectTag ( tag ) ; this . clearValue ( tagSearchElement ) ; tagSearchElement . value = tag . value ; this . currentValue = tag . value ; this . changed . emit ( { item : this . selectedTag , text : this . currentValue , sourceItem : this . getSourceItem ( tag ) } ) ; } getSourceItem ( tag ) { var sourceItem = this . userSource . filter ( t => t [ this . keyValueProps [ 1 ] ] == tag . key ) [ 0 ] ; return sourceItem ; } selectTag ( tag ) { tag . active = false ; this . selectedTag = tag ; this . clearActiveTabClass ( ) ; this . updateFormValue ( this . selectedTag . key ) ; } private updateFormValue ( value ) { this . propagateChange ( value ) ; } private onMouseover ( tag ? , i ? ) { this . dropSource . forEach ( t => t . active = false ) ; if ( tag && ( i != undefined ) ) { this . dropIndex = i ; this . selectActive = false ; tag . active = true ; } else this . selectActive = true ; } private clearValue ( element ) { element . value = "" ; } private clearActiveTabClass ( ) { this . activeTag = "" ; this . openDrop = "" ; this . dropSource . forEach ( tag => tag . active = false ) ; } private subscribeFormControl ( ) { this . currentFormControl . statusChanges . subscribe ( t => { this . isInValid = t === "INVALID" ; } ) ; } validate ( formControl ) { if ( ! this . currentFormControl ) { this . currentFormControl = formControl ; this . subscribeFormControl ( ) ; } return null ; } ngOnDestroy ( ) { let tString = '' ; this . mainSource = [ ] ; } } @ NgModule ( { imports : [ CommonModule , FormsModule , ReactiveFormsModule ] , exports : [ RxSelectComponent ] , declarations : [ RxSelectComponent ] } ) export class RxSelectModule { }