/*! * @license * Copyright 2016 Alfresco Software, Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { AuthenticationService, ThumbnailService } from '@alfresco/adf-core'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, QueryList, ViewEncapsulation, ViewChild, ViewChildren, ElementRef } from '@angular/core'; import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { SearchComponent } from './search.component'; import { MatListItem } from '@angular/material'; @Component({ selector: 'adf-search-control', templateUrl: './search-control.component.html', styleUrls: ['./search-control.component.scss'], animations: [ trigger('transitionMessages', [ state('active', style({ transform: 'translateX(0%)', 'margin-left': '13px' })), state('inactive', style({ transform: 'translateX(81%)'})), state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })), transition('inactive => active', animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')), transition('active => inactive', animate('300ms cubic-bezier(0.55, 0, 0.55, 0.2)')) ]) ], encapsulation: ViewEncapsulation.None, host: { class: 'adf-search-control' } }) export class SearchControlComponent implements OnInit, OnDestroy { @Input() expandable: boolean = true; @Input() highlight: boolean = false; @Input() inputType: string = 'text'; @Input() autocomplete: boolean = false; @Input() liveSearchEnabled: boolean = true; @Input() liveSearchMaxResults: number = 5; @Input() customSearchNode: QueryBody; @Output() submit: EventEmitter = new EventEmitter(); @Output() searchChange: EventEmitter = new EventEmitter(); @Output() optionClicked: EventEmitter = new EventEmitter(); @ViewChild(SearchComponent) searchAutocomplete: SearchComponent; @ViewChild('inputSearch') inputSearch: ElementRef; @ViewChildren(MatListItem) private listResultElement: QueryList; searchTerm: string = ''; subscriptAnimationState: string; private toggleSearch = new Subject(); private focusSubject = new Subject(); constructor(public authService: AuthenticationService, private thumbnailService: ThumbnailService) { this.toggleSearch.asObservable().debounceTime(100).subscribe(() => { if (this.expandable) { this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive'; if (this.subscriptAnimationState === 'inactive') { this.searchTerm = ''; this.searchAutocomplete.resetResults(); if ( document.activeElement.id === this.inputSearch.nativeElement.id) { this.inputSearch.nativeElement.blur(); } } } }); } ngOnInit() { this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation'; this.setupFocusEventHandlers(); } ngOnDestroy(): void { if (this.focusSubject) { this.focusSubject.unsubscribe(); this.focusSubject = null; } if (this.toggleSearch) { this.toggleSearch.unsubscribe(); this.toggleSearch = null; } } isLoggedIn(): boolean { return this.authService.isEcmLoggedIn(); } searchSubmit(event: any) { this.submit.emit(event); this.toggleSearchBar(); } inputChange(event: any) { this.searchChange.emit(event); } getAutoComplete(): string { return this.autocomplete ? 'on' : 'off'; } getMimeTypeIcon(node: MinimalNodeEntity): string { let mimeType; if (node.entry.content && node.entry.content.mimeType) { mimeType = node.entry.content.mimeType; } if (node.entry.isFolder) { mimeType = 'folder'; } return this.thumbnailService.getMimeTypeIcon(mimeType); } isSearchBarActive() { return this.subscriptAnimationState === 'active' && this.liveSearchEnabled; } toggleSearchBar() { if (this.toggleSearch) { this.toggleSearch.next(); } } elementClicked(item: any) { if (item.entry) { this.optionClicked.next(item); this.toggleSearchBar(); } } onFocus($event): void { this.focusSubject.next($event); } onBlur($event): void { this.focusSubject.next($event); } activateToolbar() { if (!this.isSearchBarActive()) { this.toggleSearchBar(); } } selectFirstResult() { if ( this.listResultElement && this.listResultElement.length > 0) { let firstElement: MatListItem = this.listResultElement.first; firstElement._getHostElement().focus(); } } onRowArrowDown($event: KeyboardEvent): void { let nextElement: any = this.getNextElementSibling( $event.target); if (nextElement) { nextElement.focus(); } } onRowArrowUp($event: KeyboardEvent): void { let previousElement: any = this.getPreviousElementSibling( $event.target); if (previousElement) { previousElement.focus(); }else { this.inputSearch.nativeElement.focus(); this.focusSubject.next(new FocusEvent('focus')); } } private setupFocusEventHandlers() { let focusEvents: Observable = this.focusSubject.asObservable() .debounceTime(50); focusEvents.filter(($event: any) => { return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout'); }).subscribe(() => { this.toggleSearchBar(); }); } private getNextElementSibling(node: Element): Element { return node.nextElementSibling; } private getPreviousElementSibling(node: Element): Element { return node.previousElementSibling; } }