diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 6a04ccbe1..d7536f5e4 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -49,6 +49,7 @@ import { SidenavViewsManagerDirective } from './components/layout/sidenav-views-
import { HeaderComponent } from './components/header/header.component';
import { CurrentUserComponent } from './components/current-user/current-user.component';
import { SearchInputComponent } from './components/search-input/search-input.component';
+import { SearchInputControlComponent } from './components/search-input-control/search-input-control.component';
import { SidenavComponent } from './components/sidenav/sidenav.component';
import { AboutComponent } from './components/about/about.component';
import { LocationLinkComponent } from './components/location-link/location-link.component';
@@ -108,6 +109,7 @@ import { AppStoreModule } from './store/app-store.module';
HeaderComponent,
CurrentUserComponent,
SearchInputComponent,
+ SearchInputControlComponent,
SidenavComponent,
FilesComponent,
FavoritesComponent,
diff --git a/src/app/components/search-input-control/search-input-control.component.html b/src/app/components/search-input-control/search-input-control.component.html
new file mode 100644
index 000000000..4ef43ef9c
--- /dev/null
+++ b/src/app/components/search-input-control/search-input-control.component.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+ 0"
+ (click)="clear()">clear
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item?.entry.name }}
+
+
+
+
+ {{item?.entry.createdByUser.displayName}}
+
+
+
+
+
+ {{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}
+
+
+
+
+
diff --git a/src/app/components/search-input-control/search-input-control.component.scss b/src/app/components/search-input-control/search-input-control.component.scss
new file mode 100644
index 000000000..cb1dd538d
--- /dev/null
+++ b/src/app/components/search-input-control/search-input-control.component.scss
@@ -0,0 +1,8 @@
+.adf-clear-search-icon-wrapper {
+ width: 1em;
+
+ .mat-icon {
+ font-size: 110%;
+ cursor: pointer;
+ }
+}
diff --git a/src/app/components/search-input-control/search-input-control.component.ts b/src/app/components/search-input-control/search-input-control.component.ts
new file mode 100644
index 000000000..496cb1d5b
--- /dev/null
+++ b/src/app/components/search-input-control/search-input-control.component.ts
@@ -0,0 +1,275 @@
+/*!
+ * @license
+ * Alfresco Example Content Application
+ *
+ * Copyright (C) 2005 - 2018 Alfresco Software Limited
+ *
+ * This file is part of the Alfresco Example Content Application.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * The Alfresco Example Content Application is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The Alfresco Example Content Application is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+import { 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, TemplateRef, ContentChild } from '@angular/core';
+import { MinimalNodeEntity, QueryBody } from 'alfresco-js-api';
+import { Observable } from 'rxjs/Observable';
+import { Subject } from 'rxjs/Subject';
+import { MatListItem } from '@angular/material';
+import { debounceTime } from 'rxjs/operators';
+import { EmptySearchResultComponent, SearchComponent } from '@alfresco/adf-content-services';
+
+@Component({
+ selector: 'app-search-input-control',
+ templateUrl: './search-input-control.component.html',
+ styleUrls: ['./search-input-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 SearchInputControlComponent implements OnInit, OnDestroy {
+
+ /** Toggles whether to use an expanding search control. If false
+ * then a regular input is used.
+ */
+ @Input()
+ expandable = true;
+
+ /** Toggles highlighting of the search term in the results. */
+ @Input()
+ highlight = false;
+
+ /** Type of the input field to render, e.g. "search" or "text" (default). */
+ @Input()
+ inputType = 'text';
+
+ /** Toggles auto-completion of the search input field. */
+ @Input()
+ autocomplete = false;
+
+ /** Toggles "find-as-you-type" suggestions for possible matches. */
+ @Input()
+ liveSearchEnabled = true;
+
+ /** Maximum number of results to show in the live search. */
+ @Input()
+ liveSearchMaxResults = 5;
+
+ /** @deprecated in 2.1.0 */
+ @Input()
+ customQueryBody: QueryBody;
+
+ /** Emitted when the search is submitted pressing ENTER button.
+ * The search term is provided as value of the event.
+ */
+ @Output()
+ submit: EventEmitter = new EventEmitter();
+
+ /** Emitted when the search term is changed. The search term is provided
+ * in the 'value' property of the returned object. If the term is less
+ * than three characters in length then the term is truncated to an empty
+ * string.
+ */
+ @Output()
+ searchChange: EventEmitter = new EventEmitter();
+
+ /** Emitted when a file item from the list of "find-as-you-type" results is selected. */
+ @Output()
+ optionClicked: EventEmitter = new EventEmitter();
+
+ @ViewChild('search')
+ searchAutocomplete: SearchComponent;
+
+ @ViewChild('searchInput')
+ searchInput: ElementRef;
+
+ @ViewChildren(MatListItem)
+ private listResultElement: QueryList;
+
+ @ContentChild(EmptySearchResultComponent)
+ emptySearchTemplate: EmptySearchResultComponent;
+
+ searchTerm = '';
+ subscriptAnimationState: string;
+ noSearchResultTemplate: TemplateRef = null;
+ skipToggle = false;
+
+ private toggleSearch = new Subject();
+ private focusSubject = new Subject();
+
+ constructor(private thumbnailService: ThumbnailService) {
+
+ this.toggleSearch.asObservable().pipe(debounceTime(200)).subscribe(() => {
+ if (this.expandable && !this.skipToggle) {
+ this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive';
+
+ if (this.subscriptAnimationState === 'inactive') {
+ this.searchTerm = '';
+ this.searchAutocomplete.resetResults();
+ if ( document.activeElement.id === this.searchInput.nativeElement.id) {
+ this.searchInput.nativeElement.blur();
+ }
+ }
+ }
+ this.skipToggle = false;
+ });
+ }
+
+ applySearchFocus(animationDoneEvent) {
+ if (animationDoneEvent.toState === 'active') {
+ this.searchInput.nativeElement.focus();
+ }
+ }
+
+ ngOnInit() {
+ this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation';
+ this.setupFocusEventHandlers();
+ }
+
+ isNoSearchTemplatePresent(): boolean {
+ return this.emptySearchTemplate ? true : false;
+ }
+
+ ngOnDestroy(): void {
+ if (this.focusSubject) {
+ this.focusSubject.unsubscribe();
+ this.focusSubject = null;
+ }
+
+ if (this.toggleSearch) {
+ this.toggleSearch.unsubscribe();
+ this.toggleSearch = null;
+ }
+ }
+
+ 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) {
+ const firstElement: MatListItem = this.listResultElement.first;
+ firstElement._getHostElement().focus();
+ }
+ }
+
+ onRowArrowDown($event: KeyboardEvent): void {
+ const nextElement: any = this.getNextElementSibling( $event.target);
+ if (nextElement) {
+ nextElement.focus();
+ }
+ }
+
+ onRowArrowUp($event: KeyboardEvent): void {
+ const previousElement: any = this.getPreviousElementSibling( $event.target);
+ if (previousElement) {
+ previousElement.focus();
+ } else {
+ this.searchInput.nativeElement.focus();
+ this.focusSubject.next(new FocusEvent('focus'));
+ }
+ }
+
+ private setupFocusEventHandlers() {
+ const focusEvents: Observable = this.focusSubject.asObservable()
+ .debounceTime(50);
+ focusEvents.filter(($event: any) => {
+ return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
+ }).subscribe(() => {
+ this.toggleSearchBar();
+ });
+ }
+
+ clear(event: any) {
+ this.searchTerm = '';
+ this.searchChange.emit('');
+ this.skipToggle = true;
+ }
+
+ private getNextElementSibling(node: Element): Element {
+ return node.nextElementSibling;
+ }
+
+ private getPreviousElementSibling(node: Element): Element {
+ return node.previousElementSibling;
+ }
+
+}
diff --git a/src/app/components/search-input/search-input.component.html b/src/app/components/search-input/search-input.component.html
index 95fd9b69d..3b2ba7f96 100644
--- a/src/app/components/search-input/search-input.component.html
+++ b/src/app/components/search-input/search-input.component.html
@@ -1,15 +1,8 @@
-
-
-
+
diff --git a/src/app/components/search-input/search-input.component.ts b/src/app/components/search-input/search-input.component.ts
index 860d87d5e..ad8455fcc 100644
--- a/src/app/components/search-input/search-input.component.ts
+++ b/src/app/components/search-input/search-input.component.ts
@@ -29,7 +29,7 @@ import {
UrlTree
} from '@angular/router';
import { MinimalNodeEntity } from 'alfresco-js-api';
-import { SearchControlComponent } from '@alfresco/adf-content-services';
+import { SearchInputControlComponent } from '../search-input-control/search-input-control.component';
import { Store } from '@ngrx/store';
import { AppStore } from '../../store/states/app.state';
import { SearchByTermAction, ViewNodeAction, NavigateToFolder } from '../../store/actions';
@@ -46,10 +46,15 @@ export class SearchInputComponent implements OnInit {
hasNewChange = false;
navigationTimer: any;
- @ViewChild('searchControl')
- searchControl: SearchControlComponent;
+ @ViewChild('searchInputControl')
+ searchInputControl: SearchInputControlComponent;
constructor(private router: Router, private store: Store) {
+ }
+
+ ngOnInit() {
+ this.showInputValue();
+
this.router.events.filter(e => e instanceof RouterEvent).subscribe(event => {
if (event instanceof NavigationEnd) {
this.showInputValue();
@@ -57,10 +62,6 @@ export class SearchInputComponent implements OnInit {
});
}
- ngOnInit() {
- this.showInputValue();
- }
-
showInputValue() {
if (this.onSearchResults) {
@@ -73,14 +74,16 @@ export class SearchInputComponent implements OnInit {
searchedWord = urlSegments[0].parameters['q'];
}
- this.searchControl.searchTerm = searchedWord;
- this.searchControl.subscriptAnimationState = 'no-animation';
+ if (this.searchInputControl) {
+ this.searchInputControl.searchTerm = searchedWord;
+ this.searchInputControl.subscriptAnimationState = 'no-animation';
+ }
} else {
- if (this.searchControl.subscriptAnimationState === 'no-animation') {
- this.searchControl.subscriptAnimationState = 'active';
- this.searchControl.searchTerm = '';
- this.searchControl.toggleSearchBar();
+ if (this.searchInputControl.subscriptAnimationState === 'no-animation') {
+ this.searchInputControl.subscriptAnimationState = 'active';
+ this.searchInputControl.searchTerm = '';
+ this.searchInputControl.toggleSearchBar();
}
}
}
@@ -127,9 +130,7 @@ export class SearchInputComponent implements OnInit {
}
this.navigationTimer = setTimeout(() => {
- if (searchTerm) {
- this.store.dispatch(new SearchByTermAction(searchTerm));
- }
+ this.store.dispatch(new SearchByTermAction(searchTerm));
this.hasOneChange = false;
}, 1000);
}
diff --git a/src/app/components/search/search.component.ts b/src/app/components/search/search.component.ts
index 8773b5514..dca6a02c4 100644
--- a/src/app/components/search/search.component.ts
+++ b/src/app/components/search/search.component.ts
@@ -87,6 +87,8 @@ export class SearchComponent extends PageComponent implements OnInit {
if (query) {
this.queryBuilder.userQuery = query;
this.queryBuilder.update();
+ } else {
+ this.onSearchResultLoaded( {list: { pagination: { totalItems: 0 }, entries: []}} );
}
});
}