/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
import {
Component, ChangeDetectionStrategy, Input, HostBinding,
ComponentRef, Output, EventEmitter, ViewChild, ElementRef, AfterViewInit, ComponentFactoryResolver, ViewContainerRef,
OnDestroy, OnInit,
} from '@angular/core';
import { NbSearchService } from './search.service';
import { NbThemeService } from '../../services/theme.service';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/delay';
import { Router, NavigationEnd } from '@angular/router';
/**
* search-field-component is used under the hood by nb-search component
* can't be used itself
*/
@Component({
selector: 'nb-search-field',
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: [
'styles/search.component.modal-zoomin.scss',
'styles/search.component.layout-rotate.scss',
'styles/search.component.modal-move.scss',
'styles/search.component.curtain.scss',
'styles/search.component.column-curtain.scss',
'styles/search.component.modal-drop.scss',
'styles/search.component.modal-half.scss',
],
template: `
`,
})
export class NbSearchFieldComponent {
static readonly TYPE_MODAL_ZOOMIN = 'modal-zoomin';
static readonly TYPE_ROTATE_LAYOUT = 'rotate-layout';
static readonly TYPE_MODAL_MOVE = 'modal-move';
static readonly TYPE_CURTAIN = 'curtain';
static readonly TYPE_COLUMN_CURTAIN = 'column-curtain';
static readonly TYPE_MODAL_DROP = 'modal-drop';
static readonly TYPE_MODAL_HALF = 'modal-half';
@Input() searchType: string;
@Input() placeholder: string;
@Output() searchClose = new EventEmitter();
@Output() search = new EventEmitter();
@Output() tabOut = new EventEmitter();
@ViewChild('searchInput') inputElement: ElementRef;
@Input() @HostBinding('class.show') showSearch: boolean = false;
@HostBinding('class.modal-zoomin')
get modalZoomin() {
return this.searchType === NbSearchFieldComponent.TYPE_MODAL_ZOOMIN;
}
@HostBinding('class.rotate-layout')
get rotateLayout() {
return this.searchType === NbSearchFieldComponent.TYPE_ROTATE_LAYOUT;
}
@HostBinding('class.modal-move')
get modalMove() {
return this.searchType === NbSearchFieldComponent.TYPE_MODAL_MOVE;
}
@HostBinding('class.curtain')
get curtain() {
return this.searchType === NbSearchFieldComponent.TYPE_CURTAIN;
}
@HostBinding('class.column-curtain')
get columnCurtain() {
return this.searchType === NbSearchFieldComponent.TYPE_COLUMN_CURTAIN;
}
@HostBinding('class.modal-drop')
get modalDrop() {
return this.searchType === NbSearchFieldComponent.TYPE_MODAL_DROP;
}
@HostBinding('class.modal-half')
get modalHalf() {
return this.searchType === NbSearchFieldComponent.TYPE_MODAL_HALF;
}
@Input()
set type(val: any) {
this.searchType = val;
}
closeSearch() {
this.searchClose.emit(true);
}
submitSearch(term) {
if (term) {
this.search.emit(term);
}
}
}
/**
* Beautiful full-page search control.
*
* @styles
*
* search-btn-open-fg:
* search-btn-close-fg:
* search-bg:
* search-bg-secondary:
* search-text:
* search-info:
* search-dash:
* search-placeholder:
*/
@Component({
selector: 'nb-search',
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['styles/search.component.scss'],
template: `
`,
})
export class NbSearchComponent implements OnInit, AfterViewInit, OnDestroy {
/**
* Tags a search with some ID, can be later used in the search service
* to determine which search component triggered the action, if multiple searches exist on the page.
*
* @type {string}
*/
@Input() tag: string;
/**
* Search input placeholder
* @type {string}
*/
@Input() placeholder: string = 'Search...';
@HostBinding('class.show') showSearch: boolean = false;
@ViewChild('attachedSearchContainer', { read: ViewContainerRef }) attachedSearchContainer: ViewContainerRef;
private searchFieldComponentRef: ComponentRef = null;
private searchType: string = 'rotate-layout';
private activateSearchSubscription: Subscription;
private deactivateSearchSubscription: Subscription;
private routerSubscription: Subscription;
constructor(private searchService: NbSearchService,
private themeService: NbThemeService,
private componentFactoryResolver: ComponentFactoryResolver,
private router: Router) { }
/**
* Search design type, available types are
* modal-zoomin, rotate-layout, modal-move, curtain, column-curtain, modal-drop, modal-half
* @type {string}
*/
@Input()
set type(val: any) {
this.searchType = val;
}
openSearch() {
this.searchService.activateSearch(this.searchType, this.tag);
}
connectToSearchField(componentRef) {
this.searchFieldComponentRef = componentRef;
componentRef.instance.searchType = this.searchType;
componentRef.instance.placeholder = this.placeholder;
componentRef.instance.searchClose.subscribe(() => {
this.searchService.deactivateSearch(this.searchType, this.tag);
});
componentRef.instance.search.subscribe(term => {
this.searchService.submitSearch(term, this.tag);
this.searchService.deactivateSearch(this.searchType, this.tag);
});
componentRef.instance.tabOut
.subscribe(() => this.showSearch && this.searchFieldComponentRef.instance.inputElement.nativeElement.focus());
componentRef.changeDetectorRef.detectChanges();
}
createAttachedSearch(component): Observable {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
const componentRef = this.attachedSearchContainer.createComponent(componentFactory);
return Observable.of(componentRef);
}
ngOnInit() {
this.routerSubscription = this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(event => this.searchService.deactivateSearch(this.searchType, this.tag));
this.activateSearchSubscription = this.searchService.onSearchActivate().subscribe((data) => {
if (!this.tag || data.tag === this.tag) {
this.showSearch = true;
this.themeService.appendLayoutClass(this.searchType);
Observable.of(null).delay(0).subscribe(() => {
this.themeService.appendLayoutClass('with-search');
});
this.searchFieldComponentRef.instance.showSearch = true;
this.searchFieldComponentRef.instance.inputElement.nativeElement.focus();
this.searchFieldComponentRef.changeDetectorRef.detectChanges();
}
});
this.deactivateSearchSubscription = this.searchService.onSearchDeactivate().subscribe((data) => {
if (!this.tag || data.tag === this.tag) {
this.showSearch = false;
this.searchFieldComponentRef.instance.showSearch = false;
this.searchFieldComponentRef.instance.inputElement.nativeElement.value = '';
this.searchFieldComponentRef.instance.inputElement.nativeElement.blur();
this.searchFieldComponentRef.changeDetectorRef.detectChanges();
this.themeService.removeLayoutClass('with-search');
Observable.of(null).delay(500).subscribe(() => {
this.themeService.removeLayoutClass(this.searchType);
});
}
});
}
ngAfterViewInit() {
this.themeService.appendToLayoutTop(NbSearchFieldComponent)
.subscribe((componentRef: ComponentRef) => {
this.connectToSearchField(componentRef);
});
}
ngOnDestroy() {
this.activateSearchSubscription.unsubscribe();
this.deactivateSearchSubscription.unsubscribe();
this.routerSubscription.unsubscribe();
if (this.searchFieldComponentRef) {
this.searchFieldComponentRef.destroy();
}
}
}