import { Component , ElementRef , OnDestroy , OnInit , QueryList , ViewChild , ViewChildren } from '@angular/core' ; import { SearchBarTag , SearchBarTagSettings } from './search-bar-tag/search-bar-tag' ; import { ValidatorService } from '../../services/validator.service' ; import { TypeService } from '../../framework/services/type.service' ; import { CmdbType } from '../../framework/models/cmdb-type' ; import { ReplaySubject } from 'rxjs' ; import { SearchBarTagComponent } from './search-bar-tag/search-bar-tag.component' ; import { ActivatedRoute , Router } from '@angular/router' ; import { FormControl , FormGroup } from '@angular/forms' ; import { debounceTime , distinctUntilChanged , takeUntil } from 'rxjs/operators' ; import { CategoryService } from '../../framework/services/category.service' ; import { CmdbCategory } from '../../framework/models/cmdb-category' ; import { SearchService } from '../search.service' ; import * as $ from 'jquery' ; import { NumberSearchResults } from '../models/search-result' ; @ Component ( { selector : 'cmdb-search-bar' , templateUrl : './search-bar.component.html' , styleUrls : [ './search-bar.component.scss' ] } ) export class SearchBarComponent implements OnInit , OnDestroy { @ ViewChild ( 'tagInput' ) tagInput : ElementRef ; @ ViewChild ( 'inputDropdown' ) inputDropdown : ElementRef ; @ ViewChild ( 'inputSubDropdown' ) inputSubDropdown : ElementRef ; @ ViewChildren ( SearchBarTagComponent ) searchBarTagComponents : QueryList < SearchBarTagComponent > ; private currentFocus : number = 0 ; private maxTabLength : number = 5 ; public tags : SearchBarTag [ ] = [ ] ; public searchBarForm : FormGroup ; public possibleTextResults : NumberSearchResults = new NumberSearchResults ( ) ; public possibleRegexResults : NumberSearchResults = new NumberSearchResults ( ) ; public possibleTypes : CmdbType [ ] = [ ] ; public possibleCategories : CmdbCategory [ ] = [ ] ; private subscriber = new ReplaySubject < void > ( ) ; constructor ( private router , private route , private searchService , private typeService , private categoryService ) { this . searchBarForm = new FormGroup ( { inputControl : new FormControl ( '' ) } ) ; } public ngOnInit ( ) { this . syncQuerySearchParameters ( ) ; this . inputControl . valueChanges . pipe ( debounceTime ( 500 ) , distinctUntilChanged ( ) ) . subscribe ( ( changes ) => { if ( changes . trim ( ) !== '' ) { if ( ValidatorService . getRegex ( ) . test ( changes ) ) { this . searchService . getEstimateValueResults ( changes ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( counter ) => { this . possibleRegexResults = counter ; } ) ; } this . searchService . getEstimateValueResults ( ValidatorService . replaceTextWithRegex ( changes ) ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( counter ) => { this . possibleTextResults = counter ; } ) ; this . typeService . getTypesByNameOrLabel ( changes ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( typeList ) => { this . possibleTypes = typeList ; } ) ; this . categoryService . getCategoriesByName ( changes ) . pipe ( takeUntil ( this . subscriber ) ) . subscribe ( ( categoryList ) => { this . possibleCategories = categoryList ; } ) ; } else { this . possibleTextResults = new NumberSearchResults ( ) ; this . possibleRegexResults = new NumberSearchResults ( ) ; this . possibleTypes = [ ] ; this . possibleCategories = [ ] ; } } ) ; } private private syncQuerySearchParameters ( ) { const searchQuery = this . route . snapshot . queryParams . query ; if ( searchQuery !== undefined ) { this . tags = JSON . parse ( searchQuery ) . filter ( tag => tag . searchForm !== 'disjunction' ) as SearchBarTag [ ] ; } else { this . tags = [ ] ; } } public get inputControl ( ) : FormControl { return this . searchBarForm . get ( 'inputControl' ) as FormControl ; } public addTag ( searchForm , params ? ) { const searchTerm = this . inputControl . value ; const tag = new SearchBarTag ( searchTerm , searchForm ) ; tag . searchLabel = searchTerm ; switch ( searchForm ) { case 'text' : tag . searchText = searchTerm . replace ( /[.*+\-?^${}()|[\]\\]/g , '\\$&' ) ; if ( ! isNaN ( parseInt ( searchTerm , 10 ) ) ) { tag . settings = { publicID : searchTerm } as SearchBarTagSettings ; } break ; case 'regex' : tag . searchText = ValidatorService . validateRegex ( searchTerm ) . trim ( ) ; break ; case 'type' : const typeIDs = [ ] ; for ( const type of params ) { typeIDs . push ( type . public_id ) ; } tag . searchLabel = params . length === 1 ? params [ 0 ] . label : searchTerm ; tag . settings = { types : typeIDs } as SearchBarTagSettings ; break ; case 'category' : const categoryIDs = [ ] ; for ( const category of params ) { categoryIDs . push ( category . public_id ) ; } tag . searchLabel = params . length === 1 ? params [ 0 ] . label : searchTerm ; tag . settings = { categories : categoryIDs } as SearchBarTagSettings ; break ; default : break ; } this . tags . push ( tag ) ; this . inputControl . setValue ( '' , { onlySelf : true } ) ; this . setFocusOnElement ( 1 ) ; } public updateTag ( changes ) { const index = this . tags . indexOf ( changes ) ; if ( index !== - 1 ) { if ( changes . searchText === '' || changes . searchText === undefined ) { this . removeTag ( changes ) ; } else { this . tags [ index ] = changes ; } } } public removeTag ( tag ) { const index = this . tags . indexOf ( tag ) ; if ( index !== - 1 ) { this . tags . splice ( index , 1 ) ; } } public removeLastTag ( ) { if ( this . inputControl . value . trim ( ) === '' ) { const index = this . tags . length - 1 ; if ( index !== - 1 ) { this . tags . splice ( index , 1 ) ; } } } public clearAll ( ) { this . tags = [ ] ; } public callSearch ( ) { if ( this . inputControl . value . trim ( ) !== '' ) { this . addTag ( 'text' ) ; } if ( this . tags . length > 0 ) { this . inputControl . setValue ( '' , { onlySelf : true } ) ; this . router . navigate ( [ '/search' ] , { queryParams : { query : JSON . stringify ( this . tags ) } , replaceUrl : true , state : { load : true } } ) ; } } public openDropdown ( currentIDx ) { $ ( '[tabindex=' + ( currentIDx ) + ']' ) . find ( '[data-toggle="collapse"]' ) . first ( ) . trigger ( 'click' ) ; } public arrowUp ( currentIDx ? ) { const nextElement = $ ( '[tabindex=' + ( currentIDx - 1 ) + ']' ) ; if ( nextElement . length > 0 ) { this . setFocusOnElement ( + nextElement . attr ( 'tabindex' ) ) ; } else { for ( let i = ( currentIDx - 1 ) ; i >= 0 ; i -- ) { const subElement = $ ( '[tabindex=' + ( i - 1 ) + ']' ) ; if ( subElement . length > 0 ) { this . setFocusOnElement ( + subElement . attr ( 'tabindex' ) ) ; break ; } } } } public arrowDown ( currentIDx ? ) { const nextElement = $ ( '[tabindex=' + ( currentIDx + 1 ) + ']' ) ; if ( nextElement . length > 0 ) { this . setFocusOnElement ( + nextElement . attr ( 'tabindex' ) ) ; } else { for ( let i = ( currentIDx + 1 ) ; i <= this . maxTabLength ; i ++ ) { const subElement = $ ( '[tabindex=' + ( i + 1 ) + ']' ) ; if ( subElement . length > 0 ) { this . setFocusOnElement ( + subElement . attr ( 'tabindex' ) ) ; break ; } } } } public setFocusOnElement ( tabIdx ? ) { if ( tabIdx ) { this . currentFocus = tabIdx ; } setTimeout ( ( ) => { $ ( '[tabindex=' + this . currentFocus + ']' ) . trigger ( 'focus' ) ; } , 0 ) ; } public ngOnDestroy ( ) { this . subscriber . next ( ) ; this . subscriber . complete ( ) ; } }