mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-06-30 18:15:11 +00:00
[ADF-1761] Search component refactoring (#2623)
* [ADF-1761] refactoring search * [ADF-1761] added click exit strategy and selection for list items * [ADF-1761] removed old search component replaced with the new implementation * [ADF-1761] removed old component and replaced with the refactored version * [ADF-1761] added the new tests for search control component * [ADF-1761] added test for refactored search component * [ADF-1761] fixed minor twitch start styling the list * [ADF-1761] fixed test * [ADF-1761] removed unused import * [ADF-1761] added search integrations with files component * [ADF-1761] rebased branch on lates development * [ADF-1761] added blur management for search * [ADF-1761] fixed wrong behaviour on blur * [ADF - 1761] fixed responsiveness on smaller resolution * [ADF-1761] reduced font and added white line * [ADF-1761] fixed some blur behaviour * [ADF-1761] added some fix for on blur behaviour * [ADF-1761] fixed some behaviour on blur * [ADF-1761] fix for angular 5 * [ADF-1761] changed name from search integration to search result into demoshell * [ADF-1761] fixed wrong change name * [ADF-1761] added documentation for search control component * [ADF-1761] added documentation for search component * [ADF-1761] fixed wrong link into documentation * [ADF-1761] fixed image for simple example * [ADF-1761] added some improvements and removed duplicated code * [ADF-1761] renamed directive to searchAutocomplete * [ADF-1761] added some changes after Peer review
This commit is contained in:
parent
e1e9c735b4
commit
4ac523df11
@ -14,7 +14,7 @@ import { SettingsComponent } from './components/settings/settings.component';
|
||||
import { AppLayoutComponent } from './components/app-layout/app-layout.component';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
import { SearchBarComponent } from './components/search/search-bar.component';
|
||||
import { SearchComponent } from './components/search/search.component';
|
||||
import { SearchResultComponent } from './components/search/search-result.component';
|
||||
import { AboutComponent } from './components/about/about.component';
|
||||
import { FormComponent } from './components/form/form.component';
|
||||
import { FormListComponent } from './components/form/form-list.component';
|
||||
@ -49,7 +49,7 @@ import { routing } from './app.routes';
|
||||
AppLayoutComponent,
|
||||
HomeComponent,
|
||||
SearchBarComponent,
|
||||
SearchComponent,
|
||||
SearchResultComponent,
|
||||
AboutComponent,
|
||||
ActivitiComponent,
|
||||
ActivitiTaskAttachmentsComponent,
|
||||
|
@ -28,7 +28,7 @@ import { ActivitiShowDiagramComponent } from './components/activiti/activiti-sho
|
||||
import { FormViewerComponent } from './components/activiti/form-viewer.component';
|
||||
import { FormNodeViewerComponent } from './components/activiti/form-node-viewer.component';
|
||||
import { ActivitiAppsViewComponent } from './components/activiti/apps-view.component';
|
||||
import { SearchComponent } from './components/search/search.component';
|
||||
import { SearchResultComponent } from './components/search/search-result.component';
|
||||
|
||||
import { DataTableComponent } from './components/datatable/datatable.component';
|
||||
import { WebscriptComponent } from './components/webscript/webscript.component';
|
||||
@ -86,7 +86,7 @@ export const appRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: SearchComponent,
|
||||
component: SearchResultComponent,
|
||||
canActivate: [AuthGuardEcm]
|
||||
},
|
||||
{
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
<div class="adf-app-layout-menu-spacer"></div>
|
||||
|
||||
<search-bar fxFlex="0 1 auto"></search-bar>
|
||||
<adf-search-bar class="adf-search-bar-overflow" fxFlex="0 1 auto"></adf-search-bar>
|
||||
|
||||
<a fxFlex="0 0 auto" class="adf-toolbar-link" fxShow fxHide.lt-md="true" mat-button data-automation-id="home" href="" routerLink="/home" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">{{'APP_LAYOUT.HOME' | translate }}</a>
|
||||
<a fxFlex="0 0 auto" class="adf-toolbar-link" fxShow fxHide.lt-md="true" mat-button data-automation-id="files" href="" routerLink="/files" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">{{'APP_LAYOUT.CONTENT_SERVICES' | translate }}</a>
|
||||
|
@ -20,6 +20,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.adf-search-bar-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-user-profile {
|
||||
margin-right: 10px;
|
||||
}
|
||||
@ -50,9 +54,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media ($mat-small) {
|
||||
@media screen and ($mat-small) {
|
||||
.adf-userinfo-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adf-search-bar-overflow {
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and ($mat-xsmall) {
|
||||
.adf-search-bar-overflow {
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -122,8 +122,10 @@
|
||||
[allowDropFiles]="true"
|
||||
[selectionMode]="selectionMode"
|
||||
[multiselect]="multiselect"
|
||||
[node]="nodeResult"
|
||||
(error)="onNavigationError($event)"
|
||||
(success)="resetError()"
|
||||
(ready)="emitReadyEvent($event)"
|
||||
(preview)="showFile($event)"
|
||||
(folderChange)="onFolderChange($event)"
|
||||
(permissionError)="handlePermissionError($event)">
|
||||
|
@ -15,10 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeDetectorRef, Component, Input, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core';
|
||||
import { Component, Input, OnInit, OnChanges, OnDestroy, ChangeDetectorRef,
|
||||
EventEmitter, Optional, ViewChild, SimpleChanges, Output } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { MinimalNodeEntity, NodePaging } from 'alfresco-js-api';
|
||||
import {
|
||||
AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService,
|
||||
DownloadZipDialogComponent, FileUploadEvent, FolderCreatedEvent, LogService, NotificationService,
|
||||
@ -36,9 +37,7 @@ const DEFAULT_FOLDER_TO_SHOW = '-my-';
|
||||
templateUrl: './files.component.html',
|
||||
styleUrls: ['./files.component.scss']
|
||||
})
|
||||
export class FilesComponent implements OnInit, OnDestroy {
|
||||
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||
currentFolderId: string = DEFAULT_FOLDER_TO_SHOW;
|
||||
export class FilesComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
errorMessage: string = null;
|
||||
fileNodeId: any;
|
||||
@ -53,6 +52,10 @@ export class FilesComponent implements OnInit, OnDestroy {
|
||||
{ value: 'multiple', viewValue: 'Multiple' }
|
||||
];
|
||||
|
||||
@Input()
|
||||
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||
currentFolderId: string = DEFAULT_FOLDER_TO_SHOW;
|
||||
|
||||
@Input()
|
||||
selectionMode = 'multiple';
|
||||
|
||||
@ -83,6 +86,12 @@ export class FilesComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
enableUpload: boolean = true;
|
||||
|
||||
@Input()
|
||||
nodeResult: NodePaging;
|
||||
|
||||
@Output()
|
||||
documentListReady: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@ViewChild(DocumentListComponent)
|
||||
documentList: DocumentListComponent;
|
||||
|
||||
@ -146,6 +155,12 @@ export class FilesComponent implements OnInit, OnDestroy {
|
||||
this.onEditFolder.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.nodeResult && changes.nodeResult.currentValue) {
|
||||
this.nodeResult = <NodePaging> changes.nodeResult.currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentDocumentListNode(): MinimalNodeEntity[] {
|
||||
if (this.documentList.folderNode) {
|
||||
return [{ entry: this.documentList.folderNode }];
|
||||
@ -205,6 +220,10 @@ export class FilesComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
emitReadyEvent(event: any) {
|
||||
this.documentListReady.emit(event);
|
||||
}
|
||||
|
||||
onContentActionError(errors) {
|
||||
const errorStatusCode = JSON.parse(errors.message).error.statusCode;
|
||||
let translatedErrorMessage: any;
|
||||
|
@ -1,14 +1,8 @@
|
||||
<adf-search-control *ngIf="isLoggedIn()"
|
||||
[searchTerm]="searchTerm"
|
||||
[autocomplete]="false"
|
||||
[highlight]="true"
|
||||
(searchSubmit)="onSearchSubmit($event);"
|
||||
(searchChange)="onSearchTermChange($event);"
|
||||
(fileSelect)="onItemClicked($event)">
|
||||
<adf-search-control [highlight]="true"
|
||||
(optionClicked)="onItemClicked($event)"
|
||||
(submit)="onSearchSubmit($event)">
|
||||
</adf-search-control>
|
||||
|
||||
<adf-viewer *ngIf="fileShowed" [(showViewer)]="fileShowed"
|
||||
[fileNodeId]="fileNodeId"
|
||||
[overlayMode]="true">
|
||||
<mat-spinner></mat-spinner>
|
||||
<adf-viewer *ngIf="fileShowed" [(showViewer)]="fileShowed" [fileNodeId]="fileNodeId" [overlayMode]="true">
|
||||
<mat-spinner></mat-spinner>
|
||||
</adf-viewer>
|
||||
|
@ -0,0 +1,5 @@
|
||||
|
||||
.adf-search-elements {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
@ -15,23 +15,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
|
||||
@Component({
|
||||
selector: 'search-bar',
|
||||
templateUrl: './search-bar.component.html'
|
||||
selector: 'adf-search-bar',
|
||||
templateUrl: './search-bar.component.html',
|
||||
styleUrls: ['./search-bar.component.scss']
|
||||
})
|
||||
export class SearchBarComponent {
|
||||
|
||||
@Input()
|
||||
expandable: boolean = true;
|
||||
|
||||
@Output()
|
||||
expand = new EventEmitter();
|
||||
|
||||
fileNodeId: string;
|
||||
fileShowed: boolean = false;
|
||||
searchTerm: string = '';
|
||||
|
||||
@Output()
|
||||
expand = new EventEmitter();
|
||||
subscriptAnimationState: string;
|
||||
|
||||
constructor(public router: Router,
|
||||
public authService: AlfrescoAuthenticationService) {
|
||||
@ -46,22 +51,20 @@ export class SearchBarComponent {
|
||||
*
|
||||
* @param event Parameters relating to the search
|
||||
*/
|
||||
onSearchSubmit(event) {
|
||||
this.router.navigate(['/search', {
|
||||
q: event.value
|
||||
}]);
|
||||
}
|
||||
onSearchSubmit(event: KeyboardEvent) {
|
||||
let value = (event.target as HTMLInputElement).value;
|
||||
this.router.navigate(['/search', {
|
||||
q: value
|
||||
}]);
|
||||
}
|
||||
|
||||
onItemClicked(event: MinimalNodeEntity) {
|
||||
if (event.entry.isFile) {
|
||||
this.fileNodeId = event.entry.id;
|
||||
this.fileShowed = true;
|
||||
} else if (event.entry.isFolder) {
|
||||
this.router.navigate(['/files', event.entry.id]);
|
||||
}
|
||||
}
|
||||
onItemClicked(event: MinimalNodeEntity) {
|
||||
if (event.entry.isFile) {
|
||||
this.fileNodeId = event.entry.id;
|
||||
this.fileShowed = true;
|
||||
} else if (event.entry.isFolder) {
|
||||
this.router.navigate(['/files', event.entry.id]);
|
||||
}
|
||||
}
|
||||
|
||||
onSearchTermChange(event) {
|
||||
this.searchTerm = event.value;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
<adf-search [searchTerm]="searchedWord"
|
||||
(resultLoaded)="showSearchResult($event)"
|
||||
#search>
|
||||
</adf-search>
|
||||
|
||||
<adf-files-component
|
||||
[currentFolderId]="null"
|
||||
[nodeResult]="resultNodePageList"
|
||||
(documentListReady)="refreshResults($event)">
|
||||
</adf-files-component>
|
@ -0,0 +1,59 @@
|
||||
/*!
|
||||
* @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 { Component, OnInit, Optional, ViewChild } from '@angular/core';
|
||||
import { Router, ActivatedRoute, Params } from '@angular/router';
|
||||
import { NodePaging } from 'alfresco-js-api';
|
||||
import { SearchComponent } from 'ng2-alfresco-search';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-result-component',
|
||||
templateUrl: './search-result.component.html',
|
||||
styleUrls: ['./search-result.component.scss']
|
||||
})
|
||||
export class SearchResultComponent implements OnInit {
|
||||
|
||||
@ViewChild('search')
|
||||
search: SearchComponent;
|
||||
|
||||
fileNodeId: string;
|
||||
queryParamName = 'q';
|
||||
searchedWord: string = '';
|
||||
fileShowed: boolean = false;
|
||||
navigationMode: string = 'dblclick';
|
||||
resultNodePageList: NodePaging;
|
||||
|
||||
constructor(public router: Router,
|
||||
@Optional() private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.route) {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
this.searchedWord = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showSearchResult(event: NodePaging) {
|
||||
this.resultNodePageList = event;
|
||||
}
|
||||
|
||||
refreshResults(event: any) {
|
||||
this.search.reload();
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<div class="search-results-container">
|
||||
<div class="adf-search-title">{{ 'SEARCH.RESULTS'| translate }}</div>
|
||||
<adf-search
|
||||
[navigate]="false"
|
||||
(nodeDbClick)="nodeDbClick($event)"></adf-search>
|
||||
</div>
|
||||
|
||||
<div *ngIf="fileShowed">
|
||||
<adf-viewer [(showViewer)]="fileShowed"
|
||||
[fileNodeId]="fileNodeId"
|
||||
[overlayMode]="true">
|
||||
<mat-spinner></mat-spinner>
|
||||
</adf-viewer>
|
||||
</div>
|
@ -1,50 +0,0 @@
|
||||
/*!
|
||||
* @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 { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'search-component',
|
||||
templateUrl: './search.component.html',
|
||||
styleUrls: ['./search.component.scss']
|
||||
})
|
||||
export class SearchComponent {
|
||||
|
||||
fileNodeId: string;
|
||||
fileShowed: boolean = false;
|
||||
|
||||
constructor(public router: Router) {
|
||||
}
|
||||
|
||||
nodeDbClick($event: any) {
|
||||
if ($event.value.entry.isFolder) {
|
||||
this.router.navigate(['/files', $event.value.entry.id]);
|
||||
} else {
|
||||
this.showFile($event);
|
||||
}
|
||||
}
|
||||
|
||||
showFile($event) {
|
||||
if ($event.value.entry.isFile) {
|
||||
this.fileNodeId = $event.value.entry.id;
|
||||
this.fileShowed = true;
|
||||
} else {
|
||||
this.fileShowed = false;
|
||||
}
|
||||
}
|
||||
}
|
767
demo-shell-ng2/versions.json
Normal file
767
demo-shell-ng2/versions.json
Normal file
@ -0,0 +1,767 @@
|
||||
{
|
||||
"name": "myapp",
|
||||
"version": "0.0.0",
|
||||
"problems": [
|
||||
"peer dep missing: @angular/compiler@4.4.6, required by @angular/compiler-cli@4.4.6",
|
||||
"peer dep missing: @angular/core@4.4.6, required by @angular/compiler-cli@4.4.6",
|
||||
"missing: alfresco-js-api@1.9.0, required by myapp@0.0.0",
|
||||
"missing: superagent@3.4.1, required by alfresco-js-api@1.9.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"@angular/animations": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/animations@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-4.4.5.tgz"
|
||||
},
|
||||
"@angular/cdk": {
|
||||
"version": "2.0.0-beta.12",
|
||||
"from": "@angular/cdk@2.0.0-beta.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-2.0.0-beta.12.tgz"
|
||||
},
|
||||
"@angular/common": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/common@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-4.4.5.tgz"
|
||||
},
|
||||
"@angular/compiler": {
|
||||
"required": {
|
||||
"_from": "@angular/compiler@4.4.5",
|
||||
"_id": "@angular/compiler@4.4.5",
|
||||
"_integrity": "sha1-hyGlkQ8rtS8J4tQEytJk817eWQI=",
|
||||
"_location": "/@angular/compiler",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "@angular/compiler@4.4.5",
|
||||
"name": "@angular/compiler",
|
||||
"escapedName": "@angular%2fcompiler",
|
||||
"scope": "@angular",
|
||||
"rawSpec": "4.4.5",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "4.4.5"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.4.5.tgz",
|
||||
"_shasum": "8721a5910f2bb52f09e2d404cad264f35ede5902",
|
||||
"_spec": "4.4.5",
|
||||
"_where": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2",
|
||||
"author": {
|
||||
"name": "angular"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/angular/angular/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"_from": "tslib@^1.7.1",
|
||||
"_id": "tslib@1.8.0",
|
||||
"_integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==",
|
||||
"_location": "/tslib",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "tslib@1.8.0",
|
||||
"name": "tslib",
|
||||
"escapedName": "tslib",
|
||||
"rawSpec": "1.8.0",
|
||||
"saveSpec": "[Circular]",
|
||||
"fetchSpec": "1.8.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/@angular/animations",
|
||||
"/@angular/cdk",
|
||||
"/@angular/common",
|
||||
"/@angular/compiler",
|
||||
"/@angular/core",
|
||||
"/@angular/flex-layout",
|
||||
"/@angular/forms",
|
||||
"/@angular/http",
|
||||
"/@angular/material",
|
||||
"/@angular/platform-browser",
|
||||
"/@angular/platform-browser-dynamic",
|
||||
"/@angular/router",
|
||||
"/ng2-activiti-analytics/@angular/animations",
|
||||
"/ng2-activiti-analytics/@angular/cdk",
|
||||
"/ng2-activiti-analytics/@angular/common",
|
||||
"/ng2-activiti-analytics/@angular/compiler",
|
||||
"/ng2-activiti-analytics/@angular/core",
|
||||
"/ng2-activiti-analytics/@angular/forms",
|
||||
"/ng2-activiti-analytics/@angular/http",
|
||||
"/ng2-activiti-analytics/@angular/material",
|
||||
"/ng2-activiti-analytics/@angular/platform-browser",
|
||||
"/ng2-activiti-analytics/@angular/platform-browser-dynamic",
|
||||
"/ng2-activiti-analytics/@angular/router",
|
||||
"/ng2-activiti-diagrams/@angular/animations",
|
||||
"/ng2-activiti-diagrams/@angular/cdk",
|
||||
"/ng2-activiti-diagrams/@angular/common",
|
||||
"/ng2-activiti-diagrams/@angular/compiler",
|
||||
"/ng2-activiti-diagrams/@angular/core",
|
||||
"/ng2-activiti-diagrams/@angular/forms",
|
||||
"/ng2-activiti-diagrams/@angular/http",
|
||||
"/ng2-activiti-diagrams/@angular/material",
|
||||
"/ng2-activiti-diagrams/@angular/platform-browser",
|
||||
"/ng2-activiti-diagrams/@angular/platform-browser-dynamic",
|
||||
"/ng2-activiti-diagrams/@angular/router",
|
||||
"/ng2-activiti-form/@angular/animations",
|
||||
"/ng2-activiti-form/@angular/cdk",
|
||||
"/ng2-activiti-form/@angular/common",
|
||||
"/ng2-activiti-form/@angular/compiler",
|
||||
"/ng2-activiti-form/@angular/core",
|
||||
"/ng2-activiti-form/@angular/forms",
|
||||
"/ng2-activiti-form/@angular/http",
|
||||
"/ng2-activiti-form/@angular/material",
|
||||
"/ng2-activiti-form/@angular/platform-browser",
|
||||
"/ng2-activiti-form/@angular/platform-browser-dynamic",
|
||||
"/ng2-activiti-form/@angular/router",
|
||||
"/ng2-activiti-processlist/@angular/animations",
|
||||
"/ng2-activiti-processlist/@angular/cdk",
|
||||
"/ng2-activiti-processlist/@angular/common",
|
||||
"/ng2-activiti-processlist/@angular/compiler",
|
||||
"/ng2-activiti-processlist/@angular/core",
|
||||
"/ng2-activiti-processlist/@angular/forms",
|
||||
"/ng2-activiti-processlist/@angular/http",
|
||||
"/ng2-activiti-processlist/@angular/material",
|
||||
"/ng2-activiti-processlist/@angular/platform-browser",
|
||||
"/ng2-activiti-processlist/@angular/platform-browser-dynamic",
|
||||
"/ng2-activiti-processlist/@angular/router",
|
||||
"/ng2-activiti-tasklist/@angular/animations",
|
||||
"/ng2-activiti-tasklist/@angular/cdk",
|
||||
"/ng2-activiti-tasklist/@angular/common",
|
||||
"/ng2-activiti-tasklist/@angular/compiler",
|
||||
"/ng2-activiti-tasklist/@angular/core",
|
||||
"/ng2-activiti-tasklist/@angular/forms",
|
||||
"/ng2-activiti-tasklist/@angular/http",
|
||||
"/ng2-activiti-tasklist/@angular/material",
|
||||
"/ng2-activiti-tasklist/@angular/platform-browser",
|
||||
"/ng2-activiti-tasklist/@angular/platform-browser-dynamic",
|
||||
"/ng2-activiti-tasklist/@angular/router",
|
||||
"/ng2-alfresco-core/@angular/animations",
|
||||
"/ng2-alfresco-core/@angular/cdk",
|
||||
"/ng2-alfresco-core/@angular/common",
|
||||
"/ng2-alfresco-core/@angular/compiler",
|
||||
"/ng2-alfresco-core/@angular/core",
|
||||
"/ng2-alfresco-core/@angular/forms",
|
||||
"/ng2-alfresco-core/@angular/http",
|
||||
"/ng2-alfresco-core/@angular/material",
|
||||
"/ng2-alfresco-core/@angular/platform-browser",
|
||||
"/ng2-alfresco-core/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-core/@angular/router",
|
||||
"/ng2-alfresco-datatable/@angular/animations",
|
||||
"/ng2-alfresco-datatable/@angular/cdk",
|
||||
"/ng2-alfresco-datatable/@angular/common",
|
||||
"/ng2-alfresco-datatable/@angular/compiler",
|
||||
"/ng2-alfresco-datatable/@angular/core",
|
||||
"/ng2-alfresco-datatable/@angular/forms",
|
||||
"/ng2-alfresco-datatable/@angular/http",
|
||||
"/ng2-alfresco-datatable/@angular/material",
|
||||
"/ng2-alfresco-datatable/@angular/platform-browser",
|
||||
"/ng2-alfresco-datatable/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-datatable/@angular/router",
|
||||
"/ng2-alfresco-documentlist/@angular/animations",
|
||||
"/ng2-alfresco-documentlist/@angular/cdk",
|
||||
"/ng2-alfresco-documentlist/@angular/common",
|
||||
"/ng2-alfresco-documentlist/@angular/compiler",
|
||||
"/ng2-alfresco-documentlist/@angular/core",
|
||||
"/ng2-alfresco-documentlist/@angular/forms",
|
||||
"/ng2-alfresco-documentlist/@angular/http",
|
||||
"/ng2-alfresco-documentlist/@angular/material",
|
||||
"/ng2-alfresco-documentlist/@angular/platform-browser",
|
||||
"/ng2-alfresco-documentlist/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-documentlist/@angular/router",
|
||||
"/ng2-alfresco-login/@angular/animations",
|
||||
"/ng2-alfresco-login/@angular/cdk",
|
||||
"/ng2-alfresco-login/@angular/common",
|
||||
"/ng2-alfresco-login/@angular/compiler",
|
||||
"/ng2-alfresco-login/@angular/core",
|
||||
"/ng2-alfresco-login/@angular/forms",
|
||||
"/ng2-alfresco-login/@angular/http",
|
||||
"/ng2-alfresco-login/@angular/material",
|
||||
"/ng2-alfresco-login/@angular/platform-browser",
|
||||
"/ng2-alfresco-login/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-login/@angular/router",
|
||||
"/ng2-alfresco-search/@angular/animations",
|
||||
"/ng2-alfresco-search/@angular/cdk",
|
||||
"/ng2-alfresco-search/@angular/common",
|
||||
"/ng2-alfresco-search/@angular/compiler",
|
||||
"/ng2-alfresco-search/@angular/core",
|
||||
"/ng2-alfresco-search/@angular/forms",
|
||||
"/ng2-alfresco-search/@angular/http",
|
||||
"/ng2-alfresco-search/@angular/material",
|
||||
"/ng2-alfresco-search/@angular/platform-browser",
|
||||
"/ng2-alfresco-search/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-search/@angular/router",
|
||||
"/ng2-alfresco-social/@angular/animations",
|
||||
"/ng2-alfresco-social/@angular/cdk",
|
||||
"/ng2-alfresco-social/@angular/common",
|
||||
"/ng2-alfresco-social/@angular/compiler",
|
||||
"/ng2-alfresco-social/@angular/core",
|
||||
"/ng2-alfresco-social/@angular/forms",
|
||||
"/ng2-alfresco-social/@angular/http",
|
||||
"/ng2-alfresco-social/@angular/material",
|
||||
"/ng2-alfresco-social/@angular/platform-browser",
|
||||
"/ng2-alfresco-social/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-social/@angular/router",
|
||||
"/ng2-alfresco-tag/@angular/animations",
|
||||
"/ng2-alfresco-tag/@angular/cdk",
|
||||
"/ng2-alfresco-tag/@angular/common",
|
||||
"/ng2-alfresco-tag/@angular/compiler",
|
||||
"/ng2-alfresco-tag/@angular/core",
|
||||
"/ng2-alfresco-tag/@angular/forms",
|
||||
"/ng2-alfresco-tag/@angular/http",
|
||||
"/ng2-alfresco-tag/@angular/material",
|
||||
"/ng2-alfresco-tag/@angular/platform-browser",
|
||||
"/ng2-alfresco-tag/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-tag/@angular/router",
|
||||
"/ng2-alfresco-upload/@angular/animations",
|
||||
"/ng2-alfresco-upload/@angular/cdk",
|
||||
"/ng2-alfresco-upload/@angular/common",
|
||||
"/ng2-alfresco-upload/@angular/compiler",
|
||||
"/ng2-alfresco-upload/@angular/core",
|
||||
"/ng2-alfresco-upload/@angular/forms",
|
||||
"/ng2-alfresco-upload/@angular/http",
|
||||
"/ng2-alfresco-upload/@angular/material",
|
||||
"/ng2-alfresco-upload/@angular/platform-browser",
|
||||
"/ng2-alfresco-upload/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-upload/@angular/router",
|
||||
"/ng2-alfresco-userinfo/@angular/animations",
|
||||
"/ng2-alfresco-userinfo/@angular/cdk",
|
||||
"/ng2-alfresco-userinfo/@angular/common",
|
||||
"/ng2-alfresco-userinfo/@angular/compiler",
|
||||
"/ng2-alfresco-userinfo/@angular/core",
|
||||
"/ng2-alfresco-userinfo/@angular/forms",
|
||||
"/ng2-alfresco-userinfo/@angular/http",
|
||||
"/ng2-alfresco-userinfo/@angular/material",
|
||||
"/ng2-alfresco-userinfo/@angular/platform-browser",
|
||||
"/ng2-alfresco-userinfo/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-userinfo/@angular/router",
|
||||
"/ng2-alfresco-viewer/@angular/animations",
|
||||
"/ng2-alfresco-viewer/@angular/cdk",
|
||||
"/ng2-alfresco-viewer/@angular/common",
|
||||
"/ng2-alfresco-viewer/@angular/compiler",
|
||||
"/ng2-alfresco-viewer/@angular/core",
|
||||
"/ng2-alfresco-viewer/@angular/forms",
|
||||
"/ng2-alfresco-viewer/@angular/http",
|
||||
"/ng2-alfresco-viewer/@angular/material",
|
||||
"/ng2-alfresco-viewer/@angular/platform-browser",
|
||||
"/ng2-alfresco-viewer/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-viewer/@angular/router",
|
||||
"/ng2-alfresco-webscript/@angular/animations",
|
||||
"/ng2-alfresco-webscript/@angular/cdk",
|
||||
"/ng2-alfresco-webscript/@angular/common",
|
||||
"/ng2-alfresco-webscript/@angular/compiler",
|
||||
"/ng2-alfresco-webscript/@angular/core",
|
||||
"/ng2-alfresco-webscript/@angular/forms",
|
||||
"/ng2-alfresco-webscript/@angular/http",
|
||||
"/ng2-alfresco-webscript/@angular/material",
|
||||
"/ng2-alfresco-webscript/@angular/platform-browser",
|
||||
"/ng2-alfresco-webscript/@angular/platform-browser-dynamic",
|
||||
"/ng2-alfresco-webscript/@angular/router"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz",
|
||||
"_shasum": "dc604ebad64bcbf696d613da6c954aa0e7ea1eb6",
|
||||
"_spec": "1.8.0",
|
||||
"_where": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2",
|
||||
"author": {
|
||||
"name": "Microsoft Corp."
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Microsoft/TypeScript/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "Runtime library for TypeScript helper functions",
|
||||
"homepage": "http://typescriptlang.org/",
|
||||
"jsnext:main": "tslib.es6.js",
|
||||
"keywords": [
|
||||
"TypeScript",
|
||||
"Microsoft",
|
||||
"compiler",
|
||||
"language",
|
||||
"javascript",
|
||||
"tslib",
|
||||
"runtime"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "tslib.js",
|
||||
"module": "tslib.es6.js",
|
||||
"name": "tslib",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Microsoft/tslib.git"
|
||||
},
|
||||
"typings": "tslib.d.ts",
|
||||
"version": "1.8.0",
|
||||
"readme": "# tslib\r\n\r\nThis is a runtime library for [TypeScript](http://www.typescriptlang.org/) that contains all of the TypeScript helper functions.\r\n\r\nThis library is primarily used by the `--importHelpers` flag in TypeScript.\r\nWhen using `--importHelpers`, a module that uses helper functions like `__extends` and `__assign` in the following emitted file:\r\n\r\n```ts\r\nvar __assign = (this && this.__assign) || Object.assign || function(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\r\n t[p] = s[p];\r\n }\r\n return t;\r\n};\r\nexports.x = {};\r\nexports.y = __assign({}, exports.x);\r\n\r\n```\r\n\r\nwill instead be emitted as something like the following:\r\n\r\n```ts\r\nvar tslib_1 = require(\"tslib\");\r\nexports.x = {};\r\nexports.y = tslib_1.__assign({}, exports.x);\r\n```\r\n\r\nBecause this can avoid duplicate declarations of things like `__extends`, `__assign`, etc., this means delivering users smaller files on average, as well as less runtime overhead.\r\nFor optimized bundles with TypeScript, you should absolutely consider using `tslib` and `--importHelpers`.\r\n\r\n# Installing\r\n\r\nFor the latest stable version, run:\r\n\r\n## npm\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\nnpm install --save tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\nnpm install --save tslib@1.6.1\r\n```\r\n\r\n## bower\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\nbower install tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\nbower install tslib@1.6.1\r\n```\r\n\r\n## JSPM\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\njspm install tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\njspm install tslib@1.6.1\r\n```\r\n\r\n# Usage\r\n\r\nSet the `importHelpers` compiler option on the command line:\r\n\r\n```\r\ntsc --importHelpers file.ts\r\n```\r\n\r\nor in your tsconfig.json:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"importHelpers\": true\r\n }\r\n}\r\n```\r\n\r\n#### For bower and JSPM users\r\n\r\nYou will need to add a `paths` mapping for `tslib`, e.g. For Bower users:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"module\": \"amd\",\r\n \"importHelpers\": true,\r\n \"baseUrl\": \"./\",\r\n \"paths\": {\r\n \"tslib\" : [\"bower_components/tslib/tslib.d.ts\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\nFor JSPM users:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"module\": \"system\",\r\n \"importHelpers\": true,\r\n \"baseUrl\": \"./\",\r\n \"paths\": {\r\n \"tslib\" : [\"jspm_packages/npm/tslib@1.8.0/tslib.d.ts\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n# Contribute\r\n\r\nThere are many ways to [contribute](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md) to TypeScript.\r\n\r\n* [Submit bugs](https://github.com/Microsoft/TypeScript/issues) and help us verify fixes as they are checked in.\r\n* Review the [source code changes](https://github.com/Microsoft/TypeScript/pulls).\r\n* Engage with other TypeScript users and developers on [StackOverflow](http://stackoverflow.com/questions/tagged/typescript).\r\n* Join the [#typescript](http://twitter.com/#!/search/realtime/%23typescript) discussion on Twitter.\r\n* [Contribute bug fixes](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md).\r\n* Read the language specification ([docx](http://go.microsoft.com/fwlink/?LinkId=267121), [pdf](http://go.microsoft.com/fwlink/?LinkId=267238)).\r\n\r\n# Documentation\r\n\r\n* [Quick tutorial](http://www.typescriptlang.org/Tutorial)\r\n* [Programming handbook](http://www.typescriptlang.org/Handbook)\r\n* [Language specification](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md)\r\n* [Homepage](http://www.typescriptlang.org/)\r\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_args": [
|
||||
[
|
||||
"tslib@1.8.0",
|
||||
"/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2"
|
||||
]
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {},
|
||||
"_dependencies": {},
|
||||
"path": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2/node_modules/tslib",
|
||||
"error": "[Circular]",
|
||||
"extraneous": false,
|
||||
"_deduped": "tslib"
|
||||
}
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Angular - the compiler library",
|
||||
"es2015": "./@angular/compiler.js",
|
||||
"homepage": "https://github.com/angular/angular#readme",
|
||||
"license": "MIT",
|
||||
"main": "./bundles/compiler.umd.js",
|
||||
"module": "./@angular/compiler.es5.js",
|
||||
"name": "@angular/compiler",
|
||||
"peerDependencies": {
|
||||
"@angular/core": "4.4.5"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/angular/angular.git"
|
||||
},
|
||||
"typings": "./compiler.d.ts",
|
||||
"version": "4.4.5",
|
||||
"readme": "Angular\n=======\n\nThe sources for this package are in the main [Angular](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_args": [
|
||||
[
|
||||
"@angular/compiler@4.4.5",
|
||||
"/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2"
|
||||
]
|
||||
],
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {},
|
||||
"_dependencies": {
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"path": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2/node_modules/@angular/compiler",
|
||||
"error": "[Circular]",
|
||||
"extraneous": false,
|
||||
"peerMissing": [
|
||||
{
|
||||
"requiredBy": "@angular/compiler-cli@4.4.6",
|
||||
"requires": "@angular/compiler@4.4.6"
|
||||
}
|
||||
]
|
||||
},
|
||||
"peerMissing": true
|
||||
},
|
||||
"@angular/core": {
|
||||
"required": {
|
||||
"_from": "@angular/core@4.4.5",
|
||||
"_id": "@angular/core@4.4.5",
|
||||
"_integrity": "sha1-VKy8vaEXGfiDx4apBpdKvrEy8aA=",
|
||||
"_location": "/@angular/core",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "@angular/core@4.4.5",
|
||||
"name": "@angular/core",
|
||||
"escapedName": "@angular%2fcore",
|
||||
"scope": "@angular",
|
||||
"rawSpec": "4.4.5",
|
||||
"saveSpec": "[Circular]",
|
||||
"fetchSpec": "4.4.5"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/@angular/core/-/core-4.4.5.tgz",
|
||||
"_shasum": "54acbcbda11719f883c786a906974abeb132f1a0",
|
||||
"_spec": "4.4.5",
|
||||
"_where": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2",
|
||||
"author": {
|
||||
"name": "angular"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/angular/angular/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"_from": "tslib@^1.7.1",
|
||||
"_id": "tslib@1.8.0",
|
||||
"_integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==",
|
||||
"_location": "/tslib",
|
||||
"_phantomChildren": "[Circular]",
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "tslib@1.8.0",
|
||||
"name": "tslib",
|
||||
"escapedName": "tslib",
|
||||
"rawSpec": "1.8.0",
|
||||
"saveSpec": "[Circular]",
|
||||
"fetchSpec": "1.8.0"
|
||||
},
|
||||
"_requiredBy": "[Circular]",
|
||||
"_resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz",
|
||||
"_shasum": "dc604ebad64bcbf696d613da6c954aa0e7ea1eb6",
|
||||
"_spec": "1.8.0",
|
||||
"_where": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2",
|
||||
"author": "[Circular]",
|
||||
"bugs": "[Circular]",
|
||||
"bundleDependencies": false,
|
||||
"deprecated": false,
|
||||
"description": "Runtime library for TypeScript helper functions",
|
||||
"homepage": "http://typescriptlang.org/",
|
||||
"jsnext:main": "tslib.es6.js",
|
||||
"keywords": "[Circular]",
|
||||
"license": "Apache-2.0",
|
||||
"main": "tslib.js",
|
||||
"module": "tslib.es6.js",
|
||||
"name": "tslib",
|
||||
"repository": "[Circular]",
|
||||
"typings": "tslib.d.ts",
|
||||
"version": "1.8.0",
|
||||
"readme": "# tslib\r\n\r\nThis is a runtime library for [TypeScript](http://www.typescriptlang.org/) that contains all of the TypeScript helper functions.\r\n\r\nThis library is primarily used by the `--importHelpers` flag in TypeScript.\r\nWhen using `--importHelpers`, a module that uses helper functions like `__extends` and `__assign` in the following emitted file:\r\n\r\n```ts\r\nvar __assign = (this && this.__assign) || Object.assign || function(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))\r\n t[p] = s[p];\r\n }\r\n return t;\r\n};\r\nexports.x = {};\r\nexports.y = __assign({}, exports.x);\r\n\r\n```\r\n\r\nwill instead be emitted as something like the following:\r\n\r\n```ts\r\nvar tslib_1 = require(\"tslib\");\r\nexports.x = {};\r\nexports.y = tslib_1.__assign({}, exports.x);\r\n```\r\n\r\nBecause this can avoid duplicate declarations of things like `__extends`, `__assign`, etc., this means delivering users smaller files on average, as well as less runtime overhead.\r\nFor optimized bundles with TypeScript, you should absolutely consider using `tslib` and `--importHelpers`.\r\n\r\n# Installing\r\n\r\nFor the latest stable version, run:\r\n\r\n## npm\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\nnpm install --save tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\nnpm install --save tslib@1.6.1\r\n```\r\n\r\n## bower\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\nbower install tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\nbower install tslib@1.6.1\r\n```\r\n\r\n## JSPM\r\n\r\n```sh\r\n# TypeScript 2.3.3 or later\r\njspm install tslib\r\n\r\n# TypeScript 2.3.2 or earlier\r\njspm install tslib@1.6.1\r\n```\r\n\r\n# Usage\r\n\r\nSet the `importHelpers` compiler option on the command line:\r\n\r\n```\r\ntsc --importHelpers file.ts\r\n```\r\n\r\nor in your tsconfig.json:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"importHelpers\": true\r\n }\r\n}\r\n```\r\n\r\n#### For bower and JSPM users\r\n\r\nYou will need to add a `paths` mapping for `tslib`, e.g. For Bower users:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"module\": \"amd\",\r\n \"importHelpers\": true,\r\n \"baseUrl\": \"./\",\r\n \"paths\": {\r\n \"tslib\" : [\"bower_components/tslib/tslib.d.ts\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\nFor JSPM users:\r\n\r\n```json\r\n{\r\n \"compilerOptions\": {\r\n \"module\": \"system\",\r\n \"importHelpers\": true,\r\n \"baseUrl\": \"./\",\r\n \"paths\": {\r\n \"tslib\" : [\"jspm_packages/npm/tslib@1.8.0/tslib.d.ts\"]\r\n }\r\n }\r\n}\r\n```\r\n\r\n\r\n# Contribute\r\n\r\nThere are many ways to [contribute](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md) to TypeScript.\r\n\r\n* [Submit bugs](https://github.com/Microsoft/TypeScript/issues) and help us verify fixes as they are checked in.\r\n* Review the [source code changes](https://github.com/Microsoft/TypeScript/pulls).\r\n* Engage with other TypeScript users and developers on [StackOverflow](http://stackoverflow.com/questions/tagged/typescript).\r\n* Join the [#typescript](http://twitter.com/#!/search/realtime/%23typescript) discussion on Twitter.\r\n* [Contribute bug fixes](https://github.com/Microsoft/TypeScript/blob/master/CONTRIBUTING.md).\r\n* Read the language specification ([docx](http://go.microsoft.com/fwlink/?LinkId=267121), [pdf](http://go.microsoft.com/fwlink/?LinkId=267238)).\r\n\r\n# Documentation\r\n\r\n* [Quick tutorial](http://www.typescriptlang.org/Tutorial)\r\n* [Programming handbook](http://www.typescriptlang.org/Handbook)\r\n* [Language specification](https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md)\r\n* [Homepage](http://www.typescriptlang.org/)\r\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_args": "[Circular]",
|
||||
"dependencies": {},
|
||||
"devDependencies": "[Circular]",
|
||||
"optionalDependencies": "[Circular]",
|
||||
"_dependencies": "[Circular]",
|
||||
"path": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2/node_modules/tslib",
|
||||
"error": "[Circular]",
|
||||
"extraneous": false,
|
||||
"_deduped": "tslib"
|
||||
}
|
||||
},
|
||||
"deprecated": false,
|
||||
"description": "Angular - the core framework",
|
||||
"es2015": "./@angular/core.js",
|
||||
"homepage": "https://github.com/angular/angular#readme",
|
||||
"license": "MIT",
|
||||
"main": "./bundles/core.umd.js",
|
||||
"module": "./@angular/core.es5.js",
|
||||
"name": "@angular/core",
|
||||
"peerDependencies": {
|
||||
"rxjs": "^5.0.1",
|
||||
"zone.js": "^0.8.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/angular/angular.git"
|
||||
},
|
||||
"typings": "./core.d.ts",
|
||||
"version": "4.4.5",
|
||||
"readme": "Angular\n=======\n\nThe sources for this package are in the main [Angular](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo.\n\nLicense: MIT\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_args": [
|
||||
[
|
||||
"@angular/core@4.4.5",
|
||||
"/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2"
|
||||
]
|
||||
],
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {},
|
||||
"_dependencies": {
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"path": "/Users/valbano/Documents/alfresco-ng2-components/demo-shell-ng2/node_modules/@angular/core",
|
||||
"error": "[Circular]",
|
||||
"extraneous": false,
|
||||
"peerMissing": [
|
||||
{
|
||||
"requiredBy": "@angular/compiler-cli@4.4.6",
|
||||
"requires": "@angular/core@4.4.6"
|
||||
}
|
||||
]
|
||||
},
|
||||
"peerMissing": true
|
||||
},
|
||||
"@angular/flex-layout": {
|
||||
"version": "2.0.0-beta.9",
|
||||
"from": "@angular/flex-layout@2.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-2.0.0-beta.9.tgz"
|
||||
},
|
||||
"@angular/forms": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/forms@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-4.4.5.tgz"
|
||||
},
|
||||
"@angular/http": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/http@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-4.4.5.tgz"
|
||||
},
|
||||
"@angular/material": {
|
||||
"version": "2.0.0-beta.12",
|
||||
"from": "@angular/material@2.0.0-beta.12",
|
||||
"resolved": "https://registry.npmjs.org/@angular/material/-/material-2.0.0-beta.12.tgz"
|
||||
},
|
||||
"@angular/platform-browser": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/platform-browser@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-4.4.5.tgz"
|
||||
},
|
||||
"@angular/platform-browser-dynamic": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/platform-browser-dynamic@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.5.tgz"
|
||||
},
|
||||
"@angular/router": {
|
||||
"version": "4.4.5",
|
||||
"from": "@angular/router@4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-4.4.5.tgz"
|
||||
},
|
||||
"@ngx-translate/core": {
|
||||
"version": "7.0.0",
|
||||
"from": "@ngx-translate/core@7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-7.0.0.tgz"
|
||||
},
|
||||
"alfresco-js-api": {
|
||||
"version": "1.9.0",
|
||||
"from": "alfresco-js-api@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/alfresco-js-api/-/alfresco-js-api-1.9.0.tgz",
|
||||
"problems": [
|
||||
"missing: superagent@3.4.1, required by alfresco-js-api@1.9.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"event-emitter": {
|
||||
"version": "0.3.4",
|
||||
"from": "event-emitter@0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz"
|
||||
},
|
||||
"superagent": {
|
||||
"version": "3.4.1",
|
||||
"from": "superagent@3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.4.1.tgz",
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"from": "component-emitter@^1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz"
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.1.1",
|
||||
"from": "cookiejar@^2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz"
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"from": "debug@^2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.1",
|
||||
"from": "extend@^3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.1",
|
||||
"from": "form-data@^2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz"
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.1.1",
|
||||
"from": "formidable@^1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz"
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"from": "methods@^1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"from": "mime@^1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz"
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.1",
|
||||
"from": "qs@^6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"from": "readable-stream@^2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chart.js": {
|
||||
"version": "2.5.0",
|
||||
"from": "chart.js@2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.5.0.tgz"
|
||||
},
|
||||
"classlist.js": {
|
||||
"version": "1.1.20150312",
|
||||
"from": "classlist.js@1.1.20150312",
|
||||
"resolved": "https://registry.npmjs.org/classlist.js/-/classlist.js-1.1.20150312.tgz"
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.4.1",
|
||||
"from": "core-js@2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz"
|
||||
},
|
||||
"custom-event-polyfill": {
|
||||
"version": "0.3.0",
|
||||
"from": "custom-event-polyfill@0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-0.3.0.tgz"
|
||||
},
|
||||
"dialog-polyfill": {
|
||||
"version": "0.4.7",
|
||||
"from": "dialog-polyfill@0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.4.7.tgz"
|
||||
},
|
||||
"element.scrollintoviewifneeded-polyfill": {
|
||||
"version": "1.0.1",
|
||||
"from": "element.scrollintoviewifneeded-polyfill@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/element.scrollintoviewifneeded-polyfill/-/element.scrollintoviewifneeded-polyfill-1.0.1.tgz"
|
||||
},
|
||||
"hammerjs": {
|
||||
"version": "2.0.8",
|
||||
"from": "hammerjs@2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz"
|
||||
},
|
||||
"intl": {
|
||||
"version": "1.2.5",
|
||||
"from": "intl@1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz"
|
||||
},
|
||||
"material-design-lite": {
|
||||
"version": "1.2.1",
|
||||
"from": "material-design-lite@1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/material-design-lite/-/material-design-lite-1.2.1.tgz"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"from": "minimatch@3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz"
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.15.1",
|
||||
"from": "moment@2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.15.1.tgz"
|
||||
},
|
||||
"ng2-3d-editor": {
|
||||
"version": "0.0.18",
|
||||
"from": "ng2-3d-editor@0.0.18",
|
||||
"resolved": "https://registry.npmjs.org/ng2-3d-editor/-/ng2-3d-editor-0.0.18.tgz"
|
||||
},
|
||||
"ng2-activiti-analytics": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-activiti-analytics@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-activiti-analytics/-/ng2-activiti-analytics-1.9.0.tgz"
|
||||
},
|
||||
"ng2-activiti-diagrams": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-activiti-diagrams@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-activiti-diagrams/-/ng2-activiti-diagrams-1.9.0.tgz"
|
||||
},
|
||||
"ng2-activiti-form": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-activiti-form@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-activiti-form/-/ng2-activiti-form-1.9.0.tgz"
|
||||
},
|
||||
"ng2-activiti-processlist": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-activiti-processlist@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-activiti-processlist/-/ng2-activiti-processlist-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-core": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-core@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-core/-/ng2-alfresco-core-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-datatable": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-datatable@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-datatable/-/ng2-alfresco-datatable-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-documentlist": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-documentlist@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-documentlist/-/ng2-alfresco-documentlist-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-login": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-login@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-login/-/ng2-alfresco-login-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-search": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-search@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-search/-/ng2-alfresco-search-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-social": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-social@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-social/-/ng2-alfresco-social-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-tag": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-tag@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-tag/-/ng2-alfresco-tag-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-upload": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-upload@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-upload/-/ng2-alfresco-upload-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-userinfo": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-userinfo@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-userinfo/-/ng2-alfresco-userinfo-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-viewer": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-viewer@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-viewer/-/ng2-alfresco-viewer-1.9.0.tgz"
|
||||
},
|
||||
"ng2-alfresco-webscript": {
|
||||
"version": "1.9.0",
|
||||
"from": "ng2-alfresco-webscript@1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-alfresco-webscript/-/ng2-alfresco-webscript-1.9.0.tgz"
|
||||
},
|
||||
"ng2-charts": {
|
||||
"version": "1.6.0",
|
||||
"from": "ng2-charts@1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-1.6.0.tgz"
|
||||
},
|
||||
"pdfjs-dist": {
|
||||
"version": "1.5.404",
|
||||
"from": "pdfjs-dist@1.5.404",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-1.5.404.tgz"
|
||||
},
|
||||
"raphael": {
|
||||
"version": "2.2.7",
|
||||
"from": "raphael@2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/raphael/-/raphael-2.2.7.tgz"
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.10",
|
||||
"from": "reflect-metadata@0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz"
|
||||
},
|
||||
"rxjs": {
|
||||
"version": "5.1.0",
|
||||
"from": "rxjs@5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.1.0.tgz"
|
||||
},
|
||||
"web-animations-js": {
|
||||
"version": "2.3.1",
|
||||
"from": "web-animations-js@2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.1.tgz"
|
||||
},
|
||||
"zone.js": {
|
||||
"version": "0.8.12",
|
||||
"from": "zone.js@0.8.12",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.12.tgz"
|
||||
}
|
||||
}
|
||||
}
|
@ -372,7 +372,6 @@ for more information about installing and using the source code.
|
||||
|
||||
- [Search control component](search-control.component.md)
|
||||
- [Search component](search.component.md)
|
||||
- [*Search autocomplete component](../ng2-components/ng2-alfresco-search/src/components/search-autocomplete.component.ts)
|
||||
<!-- ng2-alfresco-search end -->
|
||||
|
||||
[(Back to Contents)](#contents)
|
||||
|
BIN
docs/docassets/images/search-component-complex-template.png
Normal file
BIN
docs/docassets/images/search-component-complex-template.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
docs/docassets/images/search-component-simple-template.png
Normal file
BIN
docs/docassets/images/search-component-simple-template.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.6 KiB |
BIN
docs/docassets/images/search-control-component.png
Normal file
BIN
docs/docassets/images/search-control-component.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -1,5 +1,10 @@
|
||||
# Search component
|
||||
|
||||
Displays a input text which shows find-as-you-type suggestions.
|
||||
|
||||

|
||||
|
||||
|
||||
<!-- markdown-toc start - Don't edit this section. npm run toc to generate it-->
|
||||
|
||||
<!-- toc -->
|
||||
@ -17,11 +22,9 @@
|
||||
|
||||
```html
|
||||
<adf-search-control
|
||||
[searchTerm]="searchTerm"
|
||||
inputType="search"
|
||||
(searchChange)="onSearchChange($event);"
|
||||
(searchSubmit)="onSearchSubmit($event);"
|
||||
(fileSelect)="onSearchResultSelect($event);">
|
||||
[highlight]="true"
|
||||
(optionClicked)="onItemClicked($event)"
|
||||
(submit)="onSearchSubmit($event)">
|
||||
</adf-search-control>
|
||||
```
|
||||
|
||||
@ -45,18 +48,16 @@
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| searchChange | 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. |
|
||||
| searchSubmit | Emitted when the search form is submitted. The search term is provided in the 'value' property of the returned object. |
|
||||
| fileSelect | Emitted when a file item from the list of find-as-you-type results is selected |
|
||||
| submit | Emitted when the search is submitted pressing ENTER button. The search term is provided as value of the event. |
|
||||
| optionClicked | Emitted when a file item from the list of find-as-you-type results is selected |
|
||||
|
||||
## Details
|
||||
|
||||
```html
|
||||
<adf-search-control
|
||||
[searchTerm]="searchTerm"
|
||||
inputType="search"
|
||||
(searchChange)="onSearchChange($event);"
|
||||
(searchSubmit)="onSearchSubmit($event);"
|
||||
(fileSelect)="onSearchResultSelect($event);">
|
||||
<adf-search-control
|
||||
[highlight]="true"
|
||||
(optionClicked)="onItemClicked($event)"
|
||||
(submit)="onSearchSubmit($event)">
|
||||
</adf-search-control>
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Search Results component
|
||||
|
||||
|
||||
|
||||
<!-- markdown-toc start - Don't edit this section. npm run toc to generate it-->
|
||||
|
||||
<!-- toc -->
|
||||
@ -17,7 +19,8 @@
|
||||
|
||||
```html
|
||||
<adf-search
|
||||
[searchTerm]="searchTerm">
|
||||
[searchTerm]="searchTerm"
|
||||
(resultLoaded)="showSearchResult($event)">
|
||||
</adf-search>
|
||||
```
|
||||
|
||||
@ -30,25 +33,93 @@
|
||||
| resultType | string | | Node type to filter search results by, e.g. 'cm:content', 'cm:folder' if you want only the files. |
|
||||
| maxResults | number | 20 | Maximum number of results to show in the search. |
|
||||
| resultSort | string | | Criteria to sort search results by, must be one of "name" , "modifiedAt" or "createdAt" |
|
||||
| navigationMode | string | "dblclick" | Event used to initiate a navigation action to a specific result, one of "click" or "dblclick" |
|
||||
| navigate | boolean | true | Allow documentlist to navigate or not. For more information see documentlist component's documentation |
|
||||
| displayWith | function | | Function that maps an option's value to its display value in the trigger |
|
||||
|
||||
### Events
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| preview | Emitted when user acts upon files with either single or double click (depends on `navigation-mode`), recommended for Viewer components integration |
|
||||
| nodeDbClick | Emitted when user acts upon files or folders with double click **only when `navigation-mode` is set to false**, giving more freedom then just simply previewing the file |
|
||||
| resultsLoad | Emitted when search results have fully loaded |
|
||||
| resultLoaded | Emitted when search results have fully loaded |
|
||||
|
||||
## Details
|
||||
|
||||
### Customise Search Results
|
||||
You have to add a template that will be shown when the results are loaded.
|
||||
|
||||
```html
|
||||
<adf-search
|
||||
[searchTerm]="searchTerm">
|
||||
<adf-search [searchTerm]="searchTerm">
|
||||
<ng-template let-result>
|
||||
<ul>
|
||||
<li *ngFor="let item of result?.list?.entries">
|
||||
{{ item?.entry.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
```
|
||||
The results are provided via the [$implicit variable of angular2](https://angular.io/api/common/NgTemplateOutlet) and can be accessed via the sugar sintax 'let-yourChosenName'. As per example above the result will be something like :
|
||||
|
||||

|
||||
|
||||
But you can define even a more complex template :
|
||||
|
||||
```html
|
||||
<adf-search class="adf-search-result-autocomplete"
|
||||
[rootNodeId]="liveSearchRoot"
|
||||
[resultType]="liveSearchResultType"
|
||||
[resultSort]="liveSearchResultSort"
|
||||
[maxResults]="liveSearchMaxResults">
|
||||
<ng-template let-data>
|
||||
<mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list">
|
||||
<mat-list-item
|
||||
*ngFor="let item of data?.list?.entries; let idx = index"
|
||||
id="result_option_{{idx}}"
|
||||
[tabindex]="0"
|
||||
(focus)="onFocus($event)"
|
||||
(blur)="onBlur($event)"
|
||||
class="adf-search-autocomplete-item"
|
||||
(click)="elementClicked(item)"
|
||||
(keyup.enter)="elementClicked(item)">
|
||||
<mat-icon mat-list-icon>
|
||||
<img [src]="getMimeTypeIcon(item)" />
|
||||
</mat-icon>
|
||||
<h4 mat-line id="result_name_{{idx}}"
|
||||
*ngIf="highlight; else elseBlock"
|
||||
class="adf-search-fixed-text"
|
||||
[innerHtml]="item.entry.name | highlight: searchTerm">
|
||||
{{ item?.entry.name }}</h4>
|
||||
<ng-template #elseBlock>
|
||||
<h4 class="adf-search-fixed-text" mat-line id="result_name_{{idx}}" [innerHtml]="item.entry.name"></h4>
|
||||
</ng-template>
|
||||
<p mat-line class="adf-search-fixed-text"> {{item?.entry.createdByUser.displayName}} </p>
|
||||
</mat-list-item>
|
||||
<mat-list-item
|
||||
id="search_no_result"
|
||||
*ngIf="data?.list?.entries.length === 0">
|
||||
<p mat-line class="adf-search-fixed-text">{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
```
|
||||
|
||||
Example of a component that displays search results, using the Angular2 router to supply a 'q' parameter containing the
|
||||
search term. If no router is present on the page or if the router does not provide such parameter then an empty
|
||||
results page will be shown.
|
||||
Which will look like :
|
||||
|
||||

|
||||
|
||||
|
||||
### Attach an input field to the search
|
||||
You can also attach your input field to the adf-search component via the trigger [searchAutocomplete]
|
||||
|
||||
```html
|
||||
<input type="text" [searchAutocomplete]="search">
|
||||
|
||||
<adf-search #search="searchAutocomplete">
|
||||
<ng-template let-result>
|
||||
<span *ngFor="let item of result?.list?.entries">
|
||||
{{ item?.entry.name }}
|
||||
</span>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
```
|
||||
In this way it is possible to fetch the results from the word typed into the input text straight into the adf-search component via the custom template variable.
|
@ -17,52 +17,36 @@
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatInputModule, MatListModule } from '@angular/material';
|
||||
import { CoreModule, SearchService, TRANSLATION_PROVIDER } from 'ng2-alfresco-core';
|
||||
import { DocumentListModule } from 'ng2-alfresco-documentlist';
|
||||
import { SearchAutocompleteComponent } from './src/components/search-autocomplete.component';
|
||||
import { SearchControlComponent } from './src/components/search-control.component';
|
||||
import { SearchTriggerDirective } from './src/components/search-trigger.directive';
|
||||
import { SearchComponent } from './src/components/search.component';
|
||||
|
||||
// services
|
||||
export { SearchOptions, SearchService } from 'ng2-alfresco-core';
|
||||
export * from './src/components/search.component';
|
||||
export * from './src/components/search-control.component';
|
||||
export * from './src/components/search-autocomplete.component';
|
||||
|
||||
// Old Deprecated export
|
||||
import { SearchService as AlfrescoSearchService } from 'ng2-alfresco-core';
|
||||
import { SearchAutocompleteComponent as AlfrescoSearchAutocompleteComponent } from './src/components/search-autocomplete.component';
|
||||
import { SearchControlComponent as AlfrescoSearchControlComponent } from './src/components/search-control.component';
|
||||
import { SearchComponent as AlfrescoSearchComponent } from './src/components/search.component';
|
||||
export { SearchService as AlfrescoSearchService } from 'ng2-alfresco-core';
|
||||
export { SearchComponent as AlfrescoSearchComponent } from './src/components/search.component';
|
||||
export { SearchControlComponent as AlfrescoSearchControlComponent } from './src/components/search-control.component';
|
||||
export { SearchAutocompleteComponent as AlfrescoSearchAutocompleteComponent } from './src/components/search-autocomplete.component';
|
||||
|
||||
export const ALFRESCO_SEARCH_DIRECTIVES: [any] = [
|
||||
SearchComponent,
|
||||
SearchControlComponent,
|
||||
SearchAutocompleteComponent,
|
||||
|
||||
// Old Deprecated export
|
||||
AlfrescoSearchComponent,
|
||||
AlfrescoSearchControlComponent,
|
||||
AlfrescoSearchAutocompleteComponent
|
||||
SearchTriggerDirective
|
||||
];
|
||||
|
||||
export const ALFRESCO_SEARCH_PROVIDERS: [any] = [
|
||||
SearchService,
|
||||
|
||||
// Old Deprecated export
|
||||
AlfrescoSearchService
|
||||
SearchService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
DocumentListModule,
|
||||
CoreModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
ReactiveFormsModule,
|
||||
MatListModule,
|
||||
MatInputModule
|
||||
],
|
||||
declarations: [
|
||||
...ALFRESCO_SEARCH_DIRECTIVES
|
||||
|
@ -15,6 +15,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { SearchComponent } from '../components/search.component';
|
||||
|
||||
const entryItem = {
|
||||
entry: {
|
||||
id: '123',
|
||||
@ -32,6 +35,23 @@ const entryItem = {
|
||||
}
|
||||
};
|
||||
|
||||
const entryDifferentItem = {
|
||||
entry: {
|
||||
id: '999',
|
||||
name: 'TEST_DOC',
|
||||
isFile : true,
|
||||
content: {
|
||||
mimeType: 'text/plain'
|
||||
},
|
||||
createdByUser: {
|
||||
displayName: 'John TEST'
|
||||
},
|
||||
modifiedByUser: {
|
||||
displayName: 'John TEST'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export let result = {
|
||||
list: {
|
||||
entries: [
|
||||
@ -40,6 +60,14 @@ export let result = {
|
||||
}
|
||||
};
|
||||
|
||||
export let differentResult = {
|
||||
list: {
|
||||
entries: [
|
||||
entryDifferentItem
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
export let results = {
|
||||
list: {
|
||||
entries: [
|
||||
@ -86,3 +114,56 @@ export let errorJson = {
|
||||
descriptionURL: 'https://api-explorer.alfresco.com'
|
||||
}
|
||||
};
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<adf-search [searchTerm]="searchedWord" [maxResults]="maxResults"
|
||||
(error)="showSearchResult('ERROR')"
|
||||
(success)="showSearchResult('success')" #search>
|
||||
<ng-template let-data>
|
||||
<ul id="autocomplete-search-result-list">
|
||||
<li *ngFor="let item of data?.list?.entries; let idx = index" (click)="elementClicked(item)">
|
||||
<div id="result_option_{{idx}}">
|
||||
<span>{{ item?.entry.name }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
<span id="component-result-message">{{message}}</span>
|
||||
`
|
||||
})
|
||||
|
||||
export class SimpleSearchTestComponent {
|
||||
|
||||
@ViewChild('search')
|
||||
search: SearchComponent;
|
||||
|
||||
message: string = '';
|
||||
searchedWord= '';
|
||||
maxResults: number = 5;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
showSearchResult(event: any) {
|
||||
this.message = event;
|
||||
}
|
||||
|
||||
elementClicked(event: any) {
|
||||
this.message = 'element clicked';
|
||||
}
|
||||
|
||||
setSearchWordTo(str: string) {
|
||||
this.searchedWord = str;
|
||||
}
|
||||
|
||||
changeMaxResultTo(newMax: number) {
|
||||
this.maxResults = newMax;
|
||||
}
|
||||
|
||||
forceHidePanel() {
|
||||
this.search.hidePanel();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
<table class="full-width adf-search-result"
|
||||
[@transformAutocomplete]="panelAnimationState"
|
||||
(@transformAutocomplete.done)="onAnimationDone($event)">
|
||||
<tbody id="adf-search-results" #resultsTableBody data-automation-id="autocomplete_results" *ngIf="results && results.length && searchTerm">
|
||||
<tr id="result_row_{{idx}}" *ngFor="let result of results; let idx = index" tabindex="0"
|
||||
(blur)="onRowBlur($event)" (focus)="onRowFocus($event)"
|
||||
(click)="onItemClick(result)"
|
||||
(keyup.enter)="onRowEnter(result)"
|
||||
(keyup.arrowdown)="onRowArrowDown($event)"
|
||||
(keyup.arrowup)="onRowArrowUp($event)"
|
||||
(keyup.escape)="onRowEscape($event)"
|
||||
attr.data-automation-id="autocomplete_result_for_{{result.entry.name}}">
|
||||
<td class="img-td"><img src="{{getMimeTypeIcon(result)}}" alt="{{result.entry.name}}"/></td>
|
||||
<td>
|
||||
<div id="result_name_{{idx}}" *ngIf="highlight; else elseBlock" class="truncate"
|
||||
[innerHtml]="result.entry.name | highlight: searchTerm"></div>
|
||||
<ng-template #elseBlock>
|
||||
<div id="result_name_{{idx}}" class="truncate" [innerHtml]="result.entry.name"></div>
|
||||
</ng-template>
|
||||
<div id="result_user_{{idx}}" class="truncate">{{result.entry.createdByUser.displayName}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody id="search_no_result" data-automation-id="search_no_result_found" *ngIf="results && results.length === 0">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="truncate"><b> {{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}</b></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody data-automation-id="autocomplete_error_message" *ngIf="errorMessage">
|
||||
<tr>
|
||||
<td>{{ 'SEARCH.RESULTS.ERROR' | translate:{errorMessage: errorMessage} }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -1,78 +0,0 @@
|
||||
@mixin adf-search-autocomplete-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$mat-menu-border-radius: 2px !default;
|
||||
|
||||
.adf {
|
||||
|
||||
&--search-result-container{
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
&-search-result {
|
||||
@include mat-menu-base(2);
|
||||
transform-origin: top left;
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
color: mat-color($foreground, text);
|
||||
background-color: mat-color($background, card);
|
||||
margin: -21px 0 0 0;
|
||||
border-radius: $mat-menu-border-radius;
|
||||
border-collapse: collapse;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: mat-color($foreground, text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
height: 32px;
|
||||
|
||||
&:hover {
|
||||
background-color: mat-color($background, hover);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
height: 32px;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: mat-color($primary, 900);
|
||||
}
|
||||
|
||||
.img-td {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
width: 240px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
:host {
|
||||
right: 0;
|
||||
}
|
||||
.truncate {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,428 +0,0 @@
|
||||
/*!
|
||||
* @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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ThumbnailService } from 'ng2-alfresco-core';
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoContentService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoTranslationService,
|
||||
CoreModule,
|
||||
SearchService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { errorJson, folderResult, noResult, result, results } from './../assets/search.component.mock';
|
||||
import { TranslationMock } from './../assets/translation.service.mock';
|
||||
import { SearchAutocompleteComponent } from './search-autocomplete.component';
|
||||
|
||||
describe('SearchAutocompleteComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<SearchAutocompleteComponent>, element: HTMLElement;
|
||||
let component: SearchAutocompleteComponent;
|
||||
|
||||
let updateSearchTerm = (newSearchTerm: string): void => {
|
||||
let oldSearchTerm = component.searchTerm;
|
||||
component.searchTerm = newSearchTerm;
|
||||
component.ngOnChanges({searchTerm: { currentValue: newSearchTerm, previousValue: oldSearchTerm}});
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
],
|
||||
declarations: [ SearchAutocompleteComponent ], // declare the test component
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
ThumbnailService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoContentService,
|
||||
SearchService
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(SearchAutocompleteComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
describe('search results', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(SearchService);
|
||||
});
|
||||
|
||||
it('should clear results straight away when a new search term is entered', async(() => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.searchTerm = 'searchTerm';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm', previousValue: ''} });
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('tbody[data-automation-id="autocomplete_results"] tr').length).toBe(0);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display the returned search results', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect( element.querySelector('#result_user_0').innerHTML).toBe('John Doe');
|
||||
expect( element.querySelector('#result_name_0').innerHTML).toContain('MyDoc');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should highlight the searched word', (done) => {
|
||||
component.highlight = true;
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let el: any = element.querySelectorAll('tbody[data-automation-id="autocomplete_results"] tr')[1].children[1].children[0];
|
||||
expect(el.innerText).toEqual('MyDoc');
|
||||
let spanHighlight = el.children[0];
|
||||
expect(spanHighlight.classList[0]).toEqual('highlight');
|
||||
expect(spanHighlight.innerText).toEqual('My');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('My');
|
||||
|
||||
});
|
||||
|
||||
it('should limit the number of returned search results to the configured maximum', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('tbody[data-automation-id="autocomplete_results"] tr').length).toBe(2);
|
||||
done();
|
||||
});
|
||||
|
||||
component.maxResults = 2;
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should display the correct thumbnail for result items', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
let thumbnailService = fixture.debugElement.injector.get(ThumbnailService);
|
||||
spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue('fake-type-icon.svg');
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let imgEl = <any> element.querySelector('#result_row_0 img');
|
||||
expect(imgEl).not.toBeNull();
|
||||
expect(imgEl.src).toContain('fake-type-icon.svg');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should display no result if no result are returned', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(noResult));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#search_no_result')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.reject(errorJson));
|
||||
});
|
||||
|
||||
it('should display an error if an error is encountered running the search', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {}, () => {
|
||||
fixture.detectChanges();
|
||||
let resultsEl = element.querySelector('[data-automation-id="autocomplete_results"]');
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(resultsEl).toBeNull();
|
||||
expect(errorEl).not.toBeNull();
|
||||
expect(errorEl.innerText.trim()).toBe('SEARCH.RESULTS.ERROR');
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should clear errors straight away when a new search is performed', async(() => {
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.searchTerm = 'searchTerm2';
|
||||
component.ngOnChanges({searchTerm: { currentValue: 'searchTerm2', previousValue: 'searchTerm'} });
|
||||
fixture.detectChanges();
|
||||
let errorEl = <any> element.querySelector('[data-automation-id="autocomplete_error_message"]');
|
||||
expect(errorEl).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('mouse interactions', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(SearchService);
|
||||
});
|
||||
|
||||
it('should emit fileSelect event when file item clicked', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.fileSelect.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit fileSelect event if when folder item clicked', (done) => {
|
||||
|
||||
spyOn(searchService, 'getQueryNodesPromise').and.returnValue(Promise.resolve(folderResult));
|
||||
|
||||
spyOn(component.fileSelect, 'emit');
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).click();
|
||||
expect(component.fileSelect.emit).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('keyboard interactions', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
});
|
||||
|
||||
it('should emit file select when enter key pressed when a file item is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Enter'
|
||||
}));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.fileSelect.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit cancel event when escape key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Escape'
|
||||
}));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.cancel.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should focus the next result when down arrow key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
let secondResult: any = element.querySelector('#result_row_1');
|
||||
spyOn(secondResult, 'focus');
|
||||
firstResult.focus();
|
||||
firstResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
expect(secondResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should do nothing when down arrow key pressed when the last result is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let lastResult: any = element.querySelector('#result_row_2');
|
||||
lastResult.focus();
|
||||
lastResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should focus the previous result when up arrow key pressed when a result is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
let secondResult: any = element.querySelector('#result_row_1');
|
||||
spyOn(firstResult, 'focus');
|
||||
secondResult.focus();
|
||||
secondResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
expect(firstResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
it('should emit scroll back event when up arrow key pressed and the first result is in focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
firstResult.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowUp'
|
||||
}));
|
||||
});
|
||||
|
||||
component.scrollBack.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('changing focus', () => {
|
||||
|
||||
let searchService;
|
||||
|
||||
beforeEach(() => {
|
||||
searchService = fixture.debugElement.injector.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
});
|
||||
|
||||
it('should emit a focus event when a result comes into focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('focus'));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.searchFocus.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('focus');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a focus event when a result loses focus', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
(<any> element.querySelector('#result_row_0')).dispatchEvent(new FocusEvent('blur'));
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
|
||||
component.searchFocus.subscribe((e: FocusEvent) => {
|
||||
expect(e).not.toBeNull();
|
||||
expect(e.type).toBe('blur');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should give focus to the first result when focusResult() is called externally', (done) => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
let firstResult: any = element.querySelector('#result_row_0');
|
||||
spyOn(firstResult, 'focus');
|
||||
component.focusResult();
|
||||
expect(firstResult.focus).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
|
||||
updateSearchTerm('searchTerm');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,229 +0,0 @@
|
||||
/*!
|
||||
* @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 { animate, AnimationEvent, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Component, ElementRef, EventEmitter, Input, OnChanges, Output, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { SearchOptions, SearchService } from 'ng2-alfresco-core';
|
||||
import { ThumbnailService } from 'ng2-alfresco-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-autocomplete',
|
||||
templateUrl: './search-autocomplete.component.html',
|
||||
styleUrls: ['./search-autocomplete.component.scss'],
|
||||
animations: [
|
||||
trigger('transformAutocomplete', [
|
||||
state('void', style({
|
||||
opacity: 0,
|
||||
transform: 'scale(0.01, 0.01)'
|
||||
})),
|
||||
state('enter-start', style({
|
||||
opacity: 1,
|
||||
transform: 'scale(1, 0.5)'
|
||||
})),
|
||||
state('enter', style({
|
||||
transform: 'scale(1, 1)'
|
||||
})),
|
||||
transition('void => enter-start', animate('100ms linear')),
|
||||
transition('enter-start => enter', animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)')),
|
||||
transition('* => void', animate('150ms 50ms linear', style({opacity: 0})))
|
||||
])
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SearchAutocompleteComponent implements OnChanges {
|
||||
|
||||
@Input()
|
||||
searchTerm: string = '';
|
||||
|
||||
results: any = null;
|
||||
|
||||
errorMessage: string = null;
|
||||
|
||||
@Input()
|
||||
ngClass: any;
|
||||
|
||||
@Input()
|
||||
maxResults: number = 5;
|
||||
|
||||
@Input()
|
||||
resultSort: string = null;
|
||||
|
||||
@Input()
|
||||
rootNodeId: string = '-root';
|
||||
|
||||
@Input()
|
||||
resultType: string = null;
|
||||
|
||||
@Input()
|
||||
highlight: boolean = false;
|
||||
|
||||
@Output()
|
||||
fileSelect: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
searchFocus: EventEmitter<FocusEvent> = new EventEmitter<FocusEvent>();
|
||||
|
||||
@Output()
|
||||
cancel = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
resultsLoad = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
scrollBack = new EventEmitter();
|
||||
|
||||
@ViewChild('resultsTableBody', {}) resultsTableBody: ElementRef;
|
||||
|
||||
panelAnimationState: 'void' | 'enter-start' | 'enter' = 'void';
|
||||
|
||||
constructor(private searchService: SearchService,
|
||||
private thumbnailService: ThumbnailService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (changes.searchTerm) {
|
||||
this.results = null;
|
||||
this.errorMessage = null;
|
||||
this.displaySearchResults(changes.searchTerm.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and displays search results
|
||||
* @param searchTerm Search query entered by user
|
||||
*/
|
||||
private displaySearchResults(searchTerm) {
|
||||
let searchOpts: SearchOptions = {
|
||||
include: ['path'],
|
||||
rootNodeId: this.rootNodeId,
|
||||
nodeType: this.resultType,
|
||||
maxItems: this.maxResults,
|
||||
orderBy: this.resultSort
|
||||
};
|
||||
if (searchTerm !== null && searchTerm !== '') {
|
||||
searchTerm = searchTerm + '*';
|
||||
this.searchService
|
||||
.getNodeQueryResults(searchTerm, searchOpts)
|
||||
.subscribe(
|
||||
results => {
|
||||
this.results = results.list.entries.slice(0, this.maxResults);
|
||||
|
||||
if (results && results.list) {
|
||||
this.startAnimation();
|
||||
}
|
||||
|
||||
this.errorMessage = null;
|
||||
this.resultsLoad.emit(this.results);
|
||||
},
|
||||
error => {
|
||||
this.results = null;
|
||||
this.errorMessage = <any> error;
|
||||
this.resultsLoad.error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets thumbnail URL for the given document node.
|
||||
* @param node Node to get URL for.
|
||||
* @returns {string} URL address.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
focusResult(): void {
|
||||
let firstResult: any = this.resultsTableBody.nativeElement.querySelector('tr');
|
||||
firstResult.focus();
|
||||
}
|
||||
|
||||
private getNextElementSibling(node: Element): Element {
|
||||
return node.nextElementSibling;
|
||||
}
|
||||
|
||||
private getPreviousElementSibling(node: Element): Element {
|
||||
return node.previousElementSibling;
|
||||
}
|
||||
|
||||
onItemClick(node: MinimalNodeEntity): void {
|
||||
if (node && node.entry) {
|
||||
this.fileSelect.emit(node);
|
||||
}
|
||||
}
|
||||
|
||||
onRowFocus($event: FocusEvent): void {
|
||||
this.searchFocus.emit($event);
|
||||
}
|
||||
|
||||
onRowBlur($event: FocusEvent): void {
|
||||
this.searchFocus.emit($event);
|
||||
}
|
||||
|
||||
onRowEnter(node: MinimalNodeEntity): void {
|
||||
if (node && node.entry) {
|
||||
if (node.entry.isFile) {
|
||||
this.fileSelect.emit(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowDown($event: KeyboardEvent): void {
|
||||
let nextElement: any = this.getNextElementSibling(<Element> $event.target);
|
||||
if (nextElement) {
|
||||
nextElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRowArrowUp($event: KeyboardEvent): void {
|
||||
let previousElement: any = this.getPreviousElementSibling(<Element> $event.target);
|
||||
if (previousElement) {
|
||||
previousElement.focus();
|
||||
} else {
|
||||
this.scrollBack.emit($event);
|
||||
}
|
||||
}
|
||||
|
||||
onRowEscape($event: KeyboardEvent): void {
|
||||
this.cancel.emit($event);
|
||||
}
|
||||
|
||||
startAnimation() {
|
||||
this.panelAnimationState = 'enter-start';
|
||||
}
|
||||
|
||||
resetAnimation() {
|
||||
this.panelAnimationState = 'void';
|
||||
}
|
||||
|
||||
onAnimationDone(event: AnimationEvent) {
|
||||
if (event.toState === 'enter-start') {
|
||||
this.panelAnimationState = 'enter';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +1,64 @@
|
||||
<form #f="ngForm" (ngSubmit)="onSearch()" class="adf-search-form">
|
||||
<div class="adf-search-container"
|
||||
[@transitionMessages]="subscriptAnimationState">
|
||||
<a mat-icon-button
|
||||
*ngIf="expandable"
|
||||
id="adf-search-button"
|
||||
(click)="toggleSearchBar()"
|
||||
class="adf-search-button">
|
||||
<mat-icon aria-label="search button">search</mat-icon>
|
||||
</a>
|
||||
<div class="adf-search-field">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
[type]="inputType"
|
||||
[autocomplete]="getAutoComplete()"
|
||||
data-automation-id="search_input"
|
||||
#searchInput
|
||||
id="searchControl"
|
||||
[formControl]="searchControl"
|
||||
[(ngModel)]="searchTerm"
|
||||
(focus)="onFocus($event)"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.escape)="onEscape()"
|
||||
(keyup.arrowdown)="onArrowDown()">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<adf-search-autocomplete
|
||||
#autocomplete
|
||||
*ngIf="liveSearchEnabled"
|
||||
[searchTerm]="liveSearchTerm"
|
||||
[rootNodeId]="liveSearchRoot"
|
||||
[resultType]="liveSearchResultType"
|
||||
[resultSort]="liveSearchResultSort"
|
||||
[maxResults]="liveSearchMaxResults"
|
||||
[highlight]="highlight"
|
||||
(fileSelect)="onFileClicked($event)"
|
||||
(searchFocus)="onAutoCompleteFocus($event)"
|
||||
(scrollBack)="onAutoCompleteReturn($event)"
|
||||
(cancel)="onAutoCompleteCancel($event)">
|
||||
</adf-search-autocomplete>
|
||||
<div class="adf-search-container" *ngIf="isLoggedIn()"
|
||||
[@transitionMessages]="subscriptAnimationState">
|
||||
<a mat-icon-button
|
||||
*ngIf="expandable"
|
||||
id="adf-search-button"
|
||||
class="adf-search-button"
|
||||
(click)="toggleSearchBar($event)"
|
||||
(keyup.enter)="toggleSearchBar($event)">
|
||||
<mat-icon aria-label="search button">search</mat-icon>
|
||||
</a>
|
||||
<mat-form-field class="adf-input-form-field-divider">
|
||||
<input matInput
|
||||
[type]="inputType"
|
||||
[autocomplete]="getAutoComplete()"
|
||||
id="adf-control-input"
|
||||
[(ngModel)]="searchTerm"
|
||||
(focus)="activateToolbar()"
|
||||
(blur)="onBlur($event)"
|
||||
(keyup.escape)="toggleSearchBar()"
|
||||
(ngModelChange)="inputChange($event)"
|
||||
[searchAutocomplete]="auto"
|
||||
(keyup.enter)="searchSubmit($event)">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<adf-search #auto="searchAutocomplete"
|
||||
class="adf-search-result-autocomplete"
|
||||
[rootNodeId]="liveSearchRoot"
|
||||
[resultType]="liveSearchResultType"
|
||||
[resultSort]="liveSearchResultSort"
|
||||
[maxResults]="liveSearchMaxResults">
|
||||
<ng-template let-data>
|
||||
<mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list">
|
||||
<mat-list-item
|
||||
*ngFor="let item of data?.list?.entries; let idx = index"
|
||||
id="result_option_{{idx}}"
|
||||
[tabindex]="0"
|
||||
(focus)="onFocus($event)"
|
||||
(blur)="onBlur($event)"
|
||||
class="adf-search-autocomplete-item"
|
||||
(click)="elementClicked(item)"
|
||||
(keyup.enter)="elementClicked(item)">
|
||||
<mat-icon mat-list-icon>
|
||||
<img [src]="getMimeTypeIcon(item)" />
|
||||
</mat-icon>
|
||||
<h4 mat-line id="result_name_{{idx}}"
|
||||
*ngIf="highlight; else elseBlock"
|
||||
class="adf-search-fixed-text"
|
||||
[innerHtml]="item.entry.name | highlight: searchTerm">
|
||||
{{ item?.entry.name }}</h4>
|
||||
<ng-template #elseBlock>
|
||||
<h4 class="adf-search-fixed-text" mat-line id="result_name_{{idx}}" [innerHtml]="item.entry.name"></h4>
|
||||
</ng-template>
|
||||
<p mat-line class="adf-search-fixed-text"> {{item?.entry.createdByUser.displayName}} </p>
|
||||
</mat-list-item>
|
||||
<mat-list-item
|
||||
id="search_no_result"
|
||||
data-automation-id="search_no_result_found"
|
||||
*ngIf="data?.list?.entries.length === 0">
|
||||
<p mat-line class="adf-search-fixed-text">{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}</p>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</ng-template>
|
||||
</adf-search>
|
||||
|
@ -1,51 +1,58 @@
|
||||
@mixin adf-search-control-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$mat-menu-border-radius: 2px !default;
|
||||
|
||||
.adf {
|
||||
|
||||
&-search-button.mat-icon-button {
|
||||
margin-right: 5px;
|
||||
&-search-fixed-text {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
&-search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.adf-search-field {
|
||||
max-width: 260px;
|
||||
padding-top: 6px;
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-input-underline .mat-input-ripple {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
|
||||
.mat-form-field-underline {
|
||||
background-color: mat-color($background, card);
|
||||
}
|
||||
|
||||
.mat-input-element {
|
||||
font-size: 16px;
|
||||
line-height: normal;
|
||||
padding-bottom: 2px;
|
||||
&-input-form-field-divider {
|
||||
.mat-form-field-underline {
|
||||
background-color: mat-color($primary, 50);
|
||||
.mat-form-field-ripple {
|
||||
background-color: mat-color($primary, 50);
|
||||
}
|
||||
}
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.adf-search-field .mat-input-infix {
|
||||
padding: 0;
|
||||
&-search-result-autocomplete {
|
||||
@include mat-menu-base(2);
|
||||
transform-origin: top left;
|
||||
position: absolute;
|
||||
max-width: 200px;
|
||||
max-height: 400px;
|
||||
margin-left: 45px;
|
||||
margin-top: -22px;
|
||||
font-size: 15px;
|
||||
z-index: 5;
|
||||
color: mat-color($foreground, text);
|
||||
background-color: mat-color($background, card);
|
||||
border-radius: $mat-menu-border-radius;
|
||||
|
||||
|
||||
@media screen and ($mat-small) {
|
||||
width: 160px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&-search-form{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-valid-search {
|
||||
&-search-autocomplete-item {
|
||||
&:hover {
|
||||
background-color: mat-color($background, 'hover');
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: mat-color($primary, 900);
|
||||
}
|
||||
}
|
||||
|
@ -15,28 +15,39 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatInputModule, MatListModule } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AlfrescoAuthenticationService, AlfrescoTranslationService, CoreModule, SearchService } from 'ng2-alfresco-core';
|
||||
import { ThumbnailService } from 'ng2-alfresco-core';
|
||||
import { AlfrescoTranslationService, CoreModule, SearchService } from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { noResult, results } from './../assets/search.component.mock';
|
||||
import { TranslationMock } from './../assets/translation.service.mock';
|
||||
import { SearchAutocompleteComponent } from './search-autocomplete.component';
|
||||
import { SearchControlComponent } from './search-control.component';
|
||||
import { SearchTriggerDirective } from './search-trigger.directive';
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
describe('SearchControlComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<SearchControlComponent>;
|
||||
let component: SearchControlComponent, element: HTMLElement;
|
||||
let component: SearchControlComponent;
|
||||
let element: HTMLElement;
|
||||
let debugElement: DebugElement;
|
||||
let searchService: SearchService;
|
||||
let authService: AlfrescoAuthenticationService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule
|
||||
CoreModule,
|
||||
MatInputModule,
|
||||
MatListModule
|
||||
],
|
||||
declarations: [
|
||||
SearchControlComponent,
|
||||
SearchAutocompleteComponent
|
||||
SearchComponent,
|
||||
SearchTriggerDirective
|
||||
],
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
@ -45,72 +56,93 @@ describe('SearchControlComponent', () => {
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(SearchControlComponent);
|
||||
debugElement = fixture.debugElement;
|
||||
searchService = TestBed.get(SearchService);
|
||||
authService = TestBed.get(AlfrescoAuthenticationService);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit searchChange when search term input changed', (done) => {
|
||||
fixture.componentInstance.searchChange.subscribe(e => {
|
||||
expect(e.value).toBe('customSearchTerm');
|
||||
done();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.searchTerm = 'customSearchTerm';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(async(() => {
|
||||
spyOn(authService, 'isLoggedIn').and.returnValue(true);
|
||||
}));
|
||||
|
||||
it('should emit searchChange when search term changed by user', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.searchChange.subscribe(e => {
|
||||
expect(e.value).toBe('customSearchTerm211');
|
||||
expect(e.valid).toBe(true);
|
||||
done();
|
||||
});
|
||||
component.searchControl.setValue('customSearchTerm211');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
afterEach(async(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
}));
|
||||
|
||||
it('should update FAYT search when user inputs a valid term', (done) => {
|
||||
fixture.componentInstance.searchChange.subscribe(() => {
|
||||
expect(fixture.componentInstance.liveSearchTerm).toBe('customSearchTerm');
|
||||
done();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.searchTerm = 'customSearchTerm';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
describe('when input values are inserted', () => {
|
||||
|
||||
it('should NOT update FAYT term when user inputs a search term less than 3 characters', (done) => {
|
||||
fixture.componentInstance.searchChange.subscribe(() => {
|
||||
expect(fixture.componentInstance.liveSearchTerm).toBe('');
|
||||
done();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.searchTerm = 'cu';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
beforeEach(async(() => {
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should still fire an event when user inputs a search term less than 3 characters', (done) => {
|
||||
fixture.componentInstance.searchChange.subscribe((e) => {
|
||||
expect(e.value).toBe('cu');
|
||||
expect(e.valid).toBe(false);
|
||||
done();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
fixture.componentInstance.searchTerm = 'cu';
|
||||
fixture.detectChanges();
|
||||
it('should emit searchChange when search term input changed', async(() => {
|
||||
spyOn(searchService, 'getNodeQueryResults').and.callFake(() => {
|
||||
return Observable.of({ entry: { list: []}});
|
||||
});
|
||||
component.searchChange.subscribe(value => {
|
||||
expect(value).toBe('customSearchTerm');
|
||||
});
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'customSearchTerm';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should update FAYT search when user inputs a valid term', async(() => {
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'customSearchTerm';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#result_option_0')).not.toBeNull();
|
||||
expect(element.querySelector('#result_option_1')).not.toBeNull();
|
||||
expect(element.querySelector('#result_option_2')).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT update FAYT term when user inputs a search term less than 3 characters', async(() => {
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'cu';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#result_option_0')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should still fire an event when user inputs a search term less than 3 characters', async(() => {
|
||||
component.searchChange.subscribe(value => {
|
||||
expect(value).toBe('cu');
|
||||
});
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'cu';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('expandable option false', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.expandable = false;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component.expandable = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('search button should be hide', () => {
|
||||
@ -119,288 +151,279 @@ describe('SearchControlComponent', () => {
|
||||
});
|
||||
|
||||
it('should not have animation', () => {
|
||||
component.ngOnInit();
|
||||
expect(component.subscriptAnimationState).toBe('no-animation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('component rendering', () => {
|
||||
|
||||
it('should display a text input field by default', () => {
|
||||
it('should display a text input field by default', async(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('input[type="text"]').length).toBe(1);
|
||||
});
|
||||
expect(element.querySelector('#adf-control-input')).toBeDefined();
|
||||
expect(element.querySelector('#adf-control-input')).not.toBeNull();
|
||||
}));
|
||||
|
||||
it('should display a search input field when specified', () => {
|
||||
fixture.componentInstance.inputType = 'search';
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('input[type="search"]').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should set browser autocomplete to off by default', () => {
|
||||
it('should set browser autocomplete to off by default', async(() => {
|
||||
fixture.detectChanges();
|
||||
let attr = element.querySelectorAll('input[type="text"]')[0].getAttribute('autocomplete');
|
||||
expect(attr).toBe('off');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should set browser autocomplete to on when configured', () => {
|
||||
fixture.componentInstance.autocomplete = true;
|
||||
it('should display a search input field when specified', async(() => {
|
||||
component.inputType = 'search';
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('input[type="search"]').length).toBe(1);
|
||||
}));
|
||||
|
||||
it('should set browser autocomplete to on when configured', async(() => {
|
||||
component.autocomplete = true;
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelectorAll('input[type="text"]')[0].getAttribute('autocomplete')).toBe('on');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fire a search when a enter key is pressed', async(() => {
|
||||
component.submit.subscribe((value) => {
|
||||
expect(value).toBe('TEST');
|
||||
});
|
||||
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
let enterKeyEvent: any = new Event('keyup');
|
||||
enterKeyEvent.keyCode = '13';
|
||||
inputDebugElement.nativeElement.dispatchEvent(enterKeyEvent);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('autocomplete list', () => {
|
||||
|
||||
let inputEl: HTMLInputElement;
|
||||
|
||||
beforeEach(() => {
|
||||
inputEl = element.querySelector('input');
|
||||
});
|
||||
|
||||
it('should display a autocomplete list control by default', () => {
|
||||
it('should make autocomplete list control hidden initially', async(() => {
|
||||
fixture.detectChanges();
|
||||
let autocomplete: Element = element.querySelector('adf-search-autocomplete');
|
||||
expect(autocomplete).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should make autocomplete list control hidden initially', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).toBe('void');
|
||||
});
|
||||
expect(element.querySelector('#autocomplete-search-result-list')).toBeNull();
|
||||
}));
|
||||
|
||||
it('should make autocomplete list control visible when search box has focus and there is a search result', (done) => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.liveSearchTerm = 'test';
|
||||
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new FocusEvent('focus'));
|
||||
window.setTimeout(() => {
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).not.toBe('void');
|
||||
let resultElement: Element = element.querySelector('#adf-search-results');
|
||||
let resultElement: Element = element.querySelector('#autocomplete-search-result-list');
|
||||
expect(resultElement).not.toBe(null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should show autocomplete list noe results cwhen search box has focus and there is search result with length 0', (done) => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(noResult));
|
||||
|
||||
component.liveSearchTerm = 'test';
|
||||
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new FocusEvent('focus'));
|
||||
window.setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).not.toBe('void');
|
||||
let noResultElement: Element = element.querySelector('#search_no_result');
|
||||
expect(noResultElement).not.toBe(null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should hide autocomplete list results when the search box loses focus', (done) => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.liveSearchTerm = 'test';
|
||||
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new FocusEvent('focus'));
|
||||
inputEl.dispatchEvent(new FocusEvent('blur'));
|
||||
window.setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).toBe('void');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should keep autocomplete list control visible when user tabs into results', (done) => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.liveSearchTerm = 'test';
|
||||
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new FocusEvent('focus'));
|
||||
fixture.detectChanges();
|
||||
component.onAutoCompleteFocus(new FocusEvent('focus'));
|
||||
window.setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).not.toBe('void');
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should hide autocomplete list results when escape key pressed', () => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(results));
|
||||
|
||||
component.liveSearchTerm = 'test';
|
||||
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new Event('focus'));
|
||||
inputEl.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'Escape'
|
||||
}));
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.panelAnimationState).toBe('void');
|
||||
});
|
||||
|
||||
it('should select the first result in autocomplete list when down arrow is pressed and autocomplete list is visible', (done) => {
|
||||
fixture.detectChanges();
|
||||
spyOn(component.liveSearchComponent, 'focusResult');
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new Event('focus'));
|
||||
window.setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
inputEl.dispatchEvent(new KeyboardEvent('keyup', {
|
||||
key: 'ArrowDown'
|
||||
}));
|
||||
fixture.detectChanges();
|
||||
expect(component.liveSearchComponent.focusResult).toHaveBeenCalled();
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('should focus input element when autocomplete list returns control', () => {
|
||||
fixture.detectChanges();
|
||||
spyOn(inputEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
component.onAutoCompleteReturn();
|
||||
expect(inputEl.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should focus input element when autocomplete list is cancelled', () => {
|
||||
fixture.detectChanges();
|
||||
spyOn(inputEl, 'focus');
|
||||
fixture.detectChanges();
|
||||
component.onAutoCompleteCancel();
|
||||
expect(inputEl.focus).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT display a autocomplete list control when configured not to', () => {
|
||||
fixture.componentInstance.liveSearchEnabled = false;
|
||||
fixture.detectChanges();
|
||||
let autocomplete: Element = element.querySelector('adf-search-autocomplete');
|
||||
expect(autocomplete).toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('search submit', () => {
|
||||
|
||||
it('should fire a search when a term has been entered', () => {
|
||||
spyOn(component.searchSubmit, 'emit');
|
||||
fixture.detectChanges();
|
||||
let formEl: HTMLElement = element.querySelector('form');
|
||||
component.searchTerm = 'searchTerm1';
|
||||
component.searchControl.setValue('searchTerm1');
|
||||
fixture.detectChanges();
|
||||
formEl.dispatchEvent(new Event('submit'));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchSubmit.emit).toHaveBeenCalledWith({
|
||||
'value': 'searchTerm1'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fire a search when no term has been entered', () => {
|
||||
spyOn(component.searchSubmit, 'emit');
|
||||
fixture.detectChanges();
|
||||
let inputEl: HTMLInputElement = <HTMLInputElement> element.querySelector('input[type="text"]');
|
||||
let formEl: HTMLElement = element.querySelector('form');
|
||||
inputEl.value = '';
|
||||
formEl.dispatchEvent(new Event('submit'));
|
||||
|
||||
it('should show autocomplete list noe results when search box has focus and there is search result with length 0', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(noResult));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.searchSubmit.emit).not.toHaveBeenCalled();
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'NO RES';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let noResultElement: Element = element.querySelector('#search_no_result');
|
||||
expect(noResultElement).not.toBe(null);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should hide autocomplete list results when the search box loses focus', (done) => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'NO RES';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let resultElement: Element = element.querySelector('#autocomplete-search-result-list');
|
||||
expect(resultElement).not.toBe(null);
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('blur'));
|
||||
|
||||
fixture.detectChanges();
|
||||
resultElement = element.querySelector('#autocomplete-search-result-list');
|
||||
expect(resultElement).not.toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep autocomplete list control visible when user tabs into results', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let resultElement: HTMLElement = <HTMLElement> element.querySelector('#result_option_0');
|
||||
resultElement.focus();
|
||||
expect(resultElement).not.toBe(null);
|
||||
inputDebugElement.nativeElement.dispatchEvent(new KeyboardEvent('keypress', { key: 'TAB' }));
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(element.querySelector('#autocomplete-search-result-list') ).not.toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should focus input element when autocomplete list is cancelled', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
let escapeEvent: any = new Event('ESCAPE');
|
||||
escapeEvent.keyCode = 27;
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(escapeEvent);
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(element.querySelector('#result_name_0') ).toBeNull();
|
||||
expect(document.activeElement.id).toBe(inputDebugElement.nativeElement.id);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should NOT display a autocomplete list control when configured not to', async(() => {
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
component.liveSearchEnabled = false;
|
||||
fixture.detectChanges();
|
||||
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#autocomplete-search-result-list') ).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('search button', () => {
|
||||
|
||||
it('click on the search button should close the input box when is open', (done) => {
|
||||
fixture.detectChanges();
|
||||
it('should NOT display a autocomplete list control when configured not to', async(() => {
|
||||
component.subscriptAnimationState = 'active';
|
||||
fixture.detectChanges();
|
||||
|
||||
let searchButton: any = element.querySelector('#adf-search-button');
|
||||
searchButton.click();
|
||||
|
||||
setTimeout(() => {
|
||||
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.subscriptAnimationState).toBe('inactive');
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('click on the search button should open the input box when is close', (done) => {
|
||||
fixture.detectChanges();
|
||||
component.subscriptAnimationState = 'inactive';
|
||||
|
||||
let searchButton: any = element.querySelector('#adf-search-button');
|
||||
searchButton.click();
|
||||
|
||||
setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
window.setTimeout(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.subscriptAnimationState).toBe('active');
|
||||
done();
|
||||
}, 300);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it('Search button should not change the input state too often', (done) => {
|
||||
fixture.detectChanges();
|
||||
it('Search button should not change the input state too often', async(() => {
|
||||
component.subscriptAnimationState = 'active';
|
||||
fixture.detectChanges();
|
||||
let searchButton: DebugElement = fixture.debugElement.query(By.css('#adf-search-button'));
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
searchButton.triggerEventHandler('click', null);
|
||||
fixture.detectChanges();
|
||||
|
||||
let searchButton: any = element.querySelector('#adf-search-button');
|
||||
searchButton.click();
|
||||
searchButton.click();
|
||||
|
||||
setTimeout(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.subscriptAnimationState).toBe('inactive');
|
||||
done();
|
||||
}, 400);
|
||||
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('file preview', () => {
|
||||
describe('option click', () => {
|
||||
|
||||
it('should emit a file select event when onFileClicked is called', () => {
|
||||
spyOn(component.fileSelect, 'emit');
|
||||
component.onFileClicked({
|
||||
value: 'node12345'
|
||||
it('should emit a option clicked event when item is clicked', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
component.optionClicked.subscribe((item) => {
|
||||
expect(item.entry.id).toBe('123');
|
||||
});
|
||||
expect(component.fileSelect.emit).toHaveBeenCalledWith({
|
||||
'value': 'node12345'
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
|
||||
firstOption.triggerEventHandler('click', null);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should set deactivate the search after file/folder is clicked', (done) => {
|
||||
component.subscriptAnimationState = 'active';
|
||||
component.onFileClicked({
|
||||
value: 'node12345'
|
||||
it('should set deactivate the search after element is clicked', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
component.optionClicked.subscribe((item) => {
|
||||
window.setTimeout(() => {
|
||||
expect(component.subscriptAnimationState).toBe('inactive');
|
||||
}, 200);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(component.subscriptAnimationState).toBe('inactive');
|
||||
done();
|
||||
}, 300);
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
|
||||
});
|
||||
|
||||
it('should NOT reset the search term after file/folder is clicked', () => {
|
||||
component.liveSearchTerm = 'test';
|
||||
component.onFileClicked({
|
||||
value: 'node12345'
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
|
||||
firstOption.triggerEventHandler('click', null);
|
||||
});
|
||||
}));
|
||||
|
||||
expect(component.liveSearchTerm).toBe('test');
|
||||
});
|
||||
it('should NOT reset the search term after element is clicked', async(() => {
|
||||
spyOn(component, 'isSearchBarActive').and.returnValue(true);
|
||||
spyOn(searchService, 'getNodeQueryResults').and.returnValue(Observable.of(results));
|
||||
component.optionClicked.subscribe((item) => {
|
||||
expect(component.searchTerm).not.toBeFalsy();
|
||||
expect(component.searchTerm).toBe('TEST');
|
||||
});
|
||||
fixture.detectChanges();
|
||||
let inputDebugElement = fixture.debugElement.query(By.css('#adf-control-input'));
|
||||
inputDebugElement.nativeElement.value = 'TEST';
|
||||
inputDebugElement.nativeElement.focus();
|
||||
inputDebugElement.nativeElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let firstOption: DebugElement = fixture.debugElement.query(By.css('#result_name_0'));
|
||||
firstOption.triggerEventHandler('click', null);
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -16,24 +16,11 @@
|
||||
*/
|
||||
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { FormControl, Validators } from '@angular/forms';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/map';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { MinimalNodeEntity } from 'alfresco-js-api';
|
||||
import { AlfrescoAuthenticationService, ThumbnailService } from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { SearchTermValidator } from './../forms/search-term-validator';
|
||||
import { SearchAutocompleteComponent } from './search-autocomplete.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-control',
|
||||
@ -41,56 +28,33 @@ import { SearchAutocompleteComponent } from './search-autocomplete.component';
|
||||
styleUrls: ['./search-control.component.scss'],
|
||||
animations: [
|
||||
trigger('transitionMessages', [
|
||||
state('active', style({ transform: 'translateX(0%)' })),
|
||||
state('inactive', style({ transform: 'translateX(83%)' })),
|
||||
state('no-animation', style({ transform: 'translateX(0%)', width: '100%' })),
|
||||
state('active', style({transform: 'translateX(0%)'})),
|
||||
state('inactive', style({transform: 'translateX(83%)', overflow: 'hidden'})),
|
||||
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
|
||||
]
|
||||
})
|
||||
export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input()
|
||||
searchTerm = '';
|
||||
|
||||
@Input()
|
||||
inputType = 'text';
|
||||
|
||||
@Input()
|
||||
autocomplete: boolean = false;
|
||||
|
||||
@Input()
|
||||
expandable: boolean = true;
|
||||
|
||||
@Input()
|
||||
highlight: boolean = false;
|
||||
|
||||
@Output()
|
||||
searchChange = new EventEmitter();
|
||||
@Input()
|
||||
inputType: string = 'text';
|
||||
|
||||
@Output()
|
||||
searchSubmit = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
fileSelect = new EventEmitter();
|
||||
|
||||
searchControl: FormControl;
|
||||
|
||||
@ViewChild('searchInput', {})
|
||||
searchInput: ElementRef;
|
||||
|
||||
@ViewChild(SearchAutocompleteComponent)
|
||||
liveSearchComponent: SearchAutocompleteComponent;
|
||||
@Input()
|
||||
autocomplete: boolean = false;
|
||||
|
||||
@Input()
|
||||
liveSearchEnabled: boolean = true;
|
||||
|
||||
liveSearchTerm: string = '';
|
||||
|
||||
@Input()
|
||||
liveSearchRoot: string = '-root-';
|
||||
|
||||
@ -103,21 +67,25 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
liveSearchMaxResults: number = 5;
|
||||
|
||||
searchValid = false;
|
||||
@Output()
|
||||
submit: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
private focusSubject = new Subject<FocusEvent>();
|
||||
@Output()
|
||||
searchChange: EventEmitter<string> = new EventEmitter();
|
||||
|
||||
private toggleSearch = new Subject<any>();
|
||||
@Output()
|
||||
optionClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
searchTerm: string = '';
|
||||
subscriptAnimationState: string;
|
||||
|
||||
constructor() {
|
||||
this.searchControl = new FormControl(
|
||||
this.searchTerm,
|
||||
Validators.compose([Validators.required, SearchTermValidator.minAlphanumericChars(3)])
|
||||
);
|
||||
private toggleSearch = new Subject<any>();
|
||||
private focusSubject = new Subject<FocusEvent>();
|
||||
|
||||
this.toggleSearch.asObservable().debounceTime(200).subscribe(() => {
|
||||
constructor(public authService: AlfrescoAuthenticationService,
|
||||
private thumbnailService: ThumbnailService) {
|
||||
|
||||
this.toggleSearch.asObservable().debounceTime(100).subscribe(() => {
|
||||
if (this.expandable) {
|
||||
this.subscriptAnimationState = this.subscriptAnimationState === 'inactive' ? 'active' : 'inactive';
|
||||
|
||||
@ -126,115 +94,85 @@ export class SearchControlComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.searchControl.valueChanges.subscribe((value: string) => {
|
||||
if (value) {
|
||||
this.onSearchTermChange(value);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
ngOnInit() {
|
||||
this.subscriptAnimationState = this.expandable ? 'inactive' : 'no-animation';
|
||||
this.setupFocusEventHandlers();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.focusSubject.unsubscribe();
|
||||
this.toggleSearch.unsubscribe();
|
||||
}
|
||||
|
||||
private onSearchTermChange(value: string): void {
|
||||
this.searchValid = this.searchControl.valid;
|
||||
this.liveSearchTerm = this.searchValid ? value : '';
|
||||
this.searchChange.emit({
|
||||
value: value,
|
||||
valid: this.searchValid
|
||||
});
|
||||
isLoggedIn(): boolean {
|
||||
return this.authService.isLoggedIn();
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
let focusEvents: Observable<FocusEvent> = this.focusSubject.asObservable().debounceTime(50);
|
||||
searchSubmit(event: any) {
|
||||
this.submit.emit(event);
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
|
||||
focusEvents.filter(($event: any) => {
|
||||
return $event.type === 'focusout' || $event.type === 'blur';
|
||||
}).subscribe(() => {
|
||||
this.onSearchBlur();
|
||||
});
|
||||
inputChange(event: any) {
|
||||
this.searchChange.emit(event);
|
||||
}
|
||||
|
||||
getAutoComplete(): string {
|
||||
return this.autocomplete ? 'on' : 'off';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called on form submit, i.e. when the user has hit enter
|
||||
*
|
||||
* @param event Submit event that was fired
|
||||
*/
|
||||
onSearch(): void {
|
||||
this.searchControl.setValue(this.searchTerm);
|
||||
if (this.searchControl.valid) {
|
||||
this.searchSubmit.emit({
|
||||
value: this.searchTerm
|
||||
});
|
||||
this.searchInput.nativeElement.blur();
|
||||
}
|
||||
}
|
||||
getMimeTypeIcon(node: MinimalNodeEntity): string {
|
||||
let mimeType;
|
||||
|
||||
hideAutocomplete(): void {
|
||||
if (this.liveSearchComponent) {
|
||||
this.liveSearchComponent.resetAnimation();
|
||||
}
|
||||
}
|
||||
if (node.entry.content && node.entry.content.mimeType) {
|
||||
mimeType = node.entry.content.mimeType;
|
||||
}
|
||||
if (node.entry.isFolder) {
|
||||
mimeType = 'folder';
|
||||
}
|
||||
|
||||
onFileClicked(event): void {
|
||||
this.hideAutocomplete();
|
||||
return this.thumbnailService.getMimeTypeIcon(mimeType);
|
||||
}
|
||||
|
||||
isSearchBarActive() {
|
||||
return this.subscriptAnimationState === 'active' && this.liveSearchEnabled;
|
||||
}
|
||||
|
||||
toggleSearchBar() {
|
||||
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($event) {
|
||||
if ( !this.isSearchBarActive() ) {
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
private setupFocusEventHandlers() {
|
||||
let focusEvents: Observable<FocusEvent> = this.focusSubject.asObservable()
|
||||
.distinctUntilChanged().debounceTime(50);
|
||||
focusEvents.filter(($event: any) => {
|
||||
return this.isSearchBarActive() && ($event.type === 'blur' || $event.type === 'focusout');
|
||||
}).subscribe(() => {
|
||||
this.toggleSearchBar();
|
||||
this.fileSelect.emit(event);
|
||||
this.searchTerm = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSearchBlur(): void {
|
||||
this.hideAutocomplete();
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
|
||||
onFocus($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onBlur($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onEscape(): void {
|
||||
this.hideAutocomplete();
|
||||
this.toggleSearchBar();
|
||||
}
|
||||
|
||||
onArrowDown(): void {
|
||||
this.liveSearchComponent.focusResult();
|
||||
}
|
||||
|
||||
onAutoCompleteFocus($event): void {
|
||||
this.focusSubject.next($event);
|
||||
}
|
||||
|
||||
onAutoCompleteReturn(): void {
|
||||
if (this.searchInput) {
|
||||
(<any> this.searchInput.nativeElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
onAutoCompleteCancel(): void {
|
||||
if (this.searchInput) {
|
||||
(<any> this.searchInput.nativeElement).focus();
|
||||
}
|
||||
this.hideAutocomplete();
|
||||
}
|
||||
|
||||
toggleSearchBar() {
|
||||
this.toggleSearch.next();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,215 @@
|
||||
/*!
|
||||
* @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 { DOWN_ARROW, ENTER, ESCAPE, UP_ARROW } from '@angular/cdk/keycodes';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Inject,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
Optional
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
import { DOCUMENT } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { fromEvent } from 'rxjs/observable/fromEvent';
|
||||
import { merge } from 'rxjs/observable/merge';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
export const AUTOCOMPLETE_OPTION_HEIGHT = 48;
|
||||
|
||||
export const AUTOCOMPLETE_PANEL_HEIGHT = 256;
|
||||
|
||||
export const SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SearchTriggerDirective),
|
||||
multi: true
|
||||
};
|
||||
|
||||
const MIN_WORD_LENGTH_VALID = 3;
|
||||
|
||||
@Directive({
|
||||
selector: `input[searchAutocomplete], textarea[searchAutocomplete]`,
|
||||
host: {
|
||||
'role': 'combobox',
|
||||
'autocomplete': 'off',
|
||||
'aria-autocomplete': 'list',
|
||||
'[attr.aria-expanded]': 'panelOpen.toString()',
|
||||
'[attr.aria-owns]': 'autocomplete?.id',
|
||||
'(blur)': 'onTouched()',
|
||||
'(input)': 'handleInput($event)',
|
||||
'(keydown)': 'handleKeydown($event)'
|
||||
},
|
||||
providers: [SEARCH_AUTOCOMPLETE_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SearchTriggerDirective implements ControlValueAccessor, OnDestroy {
|
||||
|
||||
@Input('searchAutocomplete')
|
||||
searchPanel: SearchComponent;
|
||||
|
||||
private _panelOpen: boolean = false;
|
||||
private closingActionsSubscription: Subscription;
|
||||
private escapeEventStream = new Subject<void>();
|
||||
|
||||
onChange: (value: any) => void = () => { };
|
||||
|
||||
onTouched = () => { };
|
||||
|
||||
constructor(private element: ElementRef,
|
||||
private ngZone: NgZone,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
@Optional() @Inject(DOCUMENT) private document: any) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this.escapeEventStream.unsubscribe();
|
||||
}
|
||||
|
||||
get panelOpen(): boolean {
|
||||
return this._panelOpen && this.searchPanel.showPanel;
|
||||
}
|
||||
|
||||
openPanel(): void {
|
||||
this.searchPanel.isOpen = this._panelOpen = true;
|
||||
this.closingActionsSubscription = this.subscribeToClosingActions();
|
||||
}
|
||||
|
||||
closePanel(): void {
|
||||
if (this._panelOpen) {
|
||||
this.closingActionsSubscription.unsubscribe();
|
||||
this._panelOpen = false;
|
||||
this.searchPanel.resetResults();
|
||||
this.searchPanel.hidePanel();
|
||||
this.changeDetectorRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
get panelClosingActions(): Observable<any> {
|
||||
return merge(
|
||||
this.escapeEventStream,
|
||||
this.outsideClickStream
|
||||
);
|
||||
}
|
||||
|
||||
private get outsideClickStream(): Observable<any> {
|
||||
if (!this.document) {
|
||||
return Observable.of(null);
|
||||
}
|
||||
|
||||
return merge(
|
||||
fromEvent(this.document, 'click'),
|
||||
fromEvent(this.document, 'touchend')
|
||||
).filter((event: MouseEvent | TouchEvent) => {
|
||||
const clickTarget = event.target as HTMLElement;
|
||||
return this._panelOpen &&
|
||||
clickTarget !== this.element.nativeElement;
|
||||
});
|
||||
}
|
||||
|
||||
writeValue(value: any): void {
|
||||
Promise.resolve(null).then(() => this.setTriggerValue(value));
|
||||
}
|
||||
|
||||
registerOnChange(fn: (value: any) => {}): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: () => {}) {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
handleKeydown(event: KeyboardEvent): void {
|
||||
const keyCode = event.keyCode;
|
||||
|
||||
if (keyCode === ESCAPE && this.panelOpen) {
|
||||
this.escapeEventStream.next();
|
||||
event.stopPropagation();
|
||||
} else if (keyCode === ENTER) {
|
||||
this.escapeEventStream.next();
|
||||
event.preventDefault();
|
||||
}else {
|
||||
let isArrowKey = keyCode === UP_ARROW || keyCode === DOWN_ARROW;
|
||||
if ( isArrowKey ) {
|
||||
if ( !this.panelOpen ) {
|
||||
this.openPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleInput(event: KeyboardEvent): void {
|
||||
if (document.activeElement === event.target) {
|
||||
let inputValue: string = (event.target as HTMLInputElement).value;
|
||||
this.onChange(inputValue);
|
||||
if (inputValue.length >= MIN_WORD_LENGTH_VALID) {
|
||||
this.searchPanel.keyPressedStream.next(inputValue);
|
||||
this.openPanel();
|
||||
} else {
|
||||
this.searchPanel.resetResults();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isPanelOptionClicked(event: MouseEvent) {
|
||||
let isPanelOption: boolean = false;
|
||||
if ( event ) {
|
||||
let clickTarget = event.target as HTMLElement;
|
||||
isPanelOption = !this.isNoResultOption(event) &&
|
||||
!!this.searchPanel.panel &&
|
||||
!!this.searchPanel.panel.nativeElement.contains(clickTarget);
|
||||
}
|
||||
return isPanelOption;
|
||||
}
|
||||
|
||||
private isNoResultOption(event: MouseEvent) {
|
||||
return this.searchPanel.results.list ? this.searchPanel.results.list.entries.length === 0 : true;
|
||||
}
|
||||
|
||||
private subscribeToClosingActions(): Subscription {
|
||||
const firstStable = this.ngZone.onStable.asObservable();
|
||||
const optionChanges = this.searchPanel.keyPressedStream.asObservable();
|
||||
|
||||
return merge(firstStable, optionChanges)
|
||||
.switchMap(() => {
|
||||
this.searchPanel.setVisibility();
|
||||
return this.panelClosingActions;
|
||||
})
|
||||
.first()
|
||||
.subscribe(event => this.setValueAndClose(event));
|
||||
}
|
||||
|
||||
private setTriggerValue(value: any): void {
|
||||
const toDisplay = this.searchPanel && this.searchPanel.displayWith ?
|
||||
this.searchPanel.displayWith(value) : value;
|
||||
const inputValue = toDisplay != null ? toDisplay : '';
|
||||
this.element.nativeElement.value = inputValue;
|
||||
}
|
||||
|
||||
private setValueAndClose(event: any | null): void {
|
||||
if (this.isPanelOptionClicked(event)) {
|
||||
this.setTriggerValue(event.target.textContent.trim());
|
||||
this.onChange(event.target.textContent.trim());
|
||||
this.element.nativeElement.focus();
|
||||
}
|
||||
this.closePanel();
|
||||
}
|
||||
}
|
@ -1,81 +1,8 @@
|
||||
<div data-automation-id="search_result_table"
|
||||
class="adf-data-table full-width">
|
||||
<p data-automation-id="search_error_message" *ngIf="errorMessage">{{ 'SEARCH.RESULTS.ERROR' | translate:{errorMessage: errorMessage} }}</p>
|
||||
<div class="container">
|
||||
<adf-document-list
|
||||
[node]="nodeResults"
|
||||
[contextMenuActions]="true"
|
||||
[contentActions]="true"
|
||||
[navigationMode]="navigationMode"
|
||||
[navigate]="navigate"
|
||||
[enablePagination]="false"
|
||||
(nodeDblClick)="onDoubleClick($event)"
|
||||
(preview)="onPreviewFile($event)">
|
||||
<empty-folder-content>
|
||||
<ng-template>
|
||||
<div class="empty_template">
|
||||
<div class="no-result-message">{{ 'SEARCH.RESULTS.NONE' | translate:{searchTerm: searchTerm} }}</div>
|
||||
<img [src]="emptyFolderImageUrl" class="no-result__empty_doc_lib">
|
||||
</div>
|
||||
</ng-template>
|
||||
</empty-folder-content>
|
||||
<data-columns>
|
||||
<data-column key="$thumbnail" type="image"></data-column>
|
||||
<data-column
|
||||
title="{{'SEARCH.DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
|
||||
key="name"
|
||||
sortable="true"
|
||||
class="full-width ellipsis-cell">
|
||||
</data-column>
|
||||
<data-column
|
||||
title="{{'SEARCH.DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
|
||||
key="createdByUser.displayName"
|
||||
sortable="true"
|
||||
class="desktop-only">
|
||||
</data-column>
|
||||
<data-column
|
||||
title="{{'SEARCH.DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
|
||||
key="createdAt"
|
||||
type="date"
|
||||
format="medium"
|
||||
sortable="true"
|
||||
class="desktop-only">
|
||||
</data-column>
|
||||
</data-columns>
|
||||
|
||||
<content-actions>
|
||||
<!-- folder actions -->
|
||||
<content-action
|
||||
target="folder"
|
||||
title="{{'SEARCH.DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
|
||||
permission="delete"
|
||||
handler="delete"
|
||||
(permissionEvent)="handlePermission($event)">
|
||||
</content-action>
|
||||
<!-- document actions -->
|
||||
<content-action
|
||||
target="document"
|
||||
title="{{'SEARCH.DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
|
||||
handler="download">
|
||||
</content-action>
|
||||
<content-action
|
||||
target="document"
|
||||
title="{{'SEARCH.DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
|
||||
permission="delete"
|
||||
handler="delete"
|
||||
(execute)="onContentDelete($event)"
|
||||
(permissionEvent)="handlePermission($event)">
|
||||
</content-action>
|
||||
</content-actions>
|
||||
</adf-document-list>
|
||||
|
||||
<adf-pagination
|
||||
(changePageSize)="onChangePageSize($event)"
|
||||
(nextPage)="onNextPage($event)"
|
||||
(prevPage)="onPrevPage($event)"
|
||||
[pagination]="pagination"
|
||||
[maxItems]="maxResults"
|
||||
[supportedPageSizes]="[5, 10, 15, 20]">
|
||||
</adf-pagination>
|
||||
</div>
|
||||
<div role="listbox" id="adf-search-results-content" [ngClass]="_classList" #panel>
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="template"
|
||||
[ngTemplateOutletContext]="{ $implicit: results }">
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -1,37 +1,19 @@
|
||||
:host .adf-data-table caption {
|
||||
margin: 0 0 16px 0;
|
||||
text-align: left;
|
||||
}
|
||||
:host .adf-data-table td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
:host .adf-data-table td.col-mimetype-icon {
|
||||
width: 24px;
|
||||
}
|
||||
:host .col-display-name {
|
||||
min-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@mixin adf-search-autocomplete-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
$mat-menu-border-radius: 2px !default;
|
||||
|
||||
.no-result-message {
|
||||
height: 32px;
|
||||
opacity: 0.26;
|
||||
font-size: 24px;
|
||||
line-height: 1.33;
|
||||
letter-spacing: -1px;
|
||||
color: #000000;
|
||||
}
|
||||
.adf {
|
||||
|
||||
.no-result__empty_doc_lib {
|
||||
width: 565px;
|
||||
height: 161px;
|
||||
object-fit: contain;
|
||||
margin-top: 17px;
|
||||
}
|
||||
&-search-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.empty_template {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
&-search-show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,377 +15,101 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement, ReflectiveInjector, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AlfrescoTranslationService, CoreModule, NotificationService, SearchService } from 'ng2-alfresco-core';
|
||||
import { DocumentListModule } from 'ng2-alfresco-documentlist';
|
||||
import { PermissionModel } from 'ng2-alfresco-documentlist';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { TranslationMock } from './../assets/translation.service.mock';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { CoreModule, SearchService } from 'ng2-alfresco-core';
|
||||
import { SearchModule } from '../../index';
|
||||
import { differentResult, result, SimpleSearchTestComponent } from './../assets/search.component.mock';
|
||||
|
||||
describe('SearchComponent', () => {
|
||||
|
||||
let fixture: ComponentFixture<SearchComponent>, element: HTMLElement;
|
||||
let component: SearchComponent;
|
||||
|
||||
let result = {
|
||||
list: {
|
||||
pagination: {
|
||||
hasMoreItems: false,
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
totalItems: 1
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: '123',
|
||||
name: 'MyDoc',
|
||||
isFile: true,
|
||||
content: {
|
||||
mimeType: 'text/plain'
|
||||
},
|
||||
createdByUser: {
|
||||
displayName: 'John Doe'
|
||||
},
|
||||
modifiedByUser: {
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let folderResult = {
|
||||
list: {
|
||||
pagination: {
|
||||
hasMoreItems: false,
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
totalItems: 1
|
||||
},
|
||||
entries: [
|
||||
{
|
||||
entry: {
|
||||
id: '123',
|
||||
name: 'MyFolder',
|
||||
isFile: false,
|
||||
isFolder: true,
|
||||
createdByUser: {
|
||||
displayName: 'John Doe'
|
||||
},
|
||||
modifiedByUser: {
|
||||
displayName: 'John Doe'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
let noResult = {
|
||||
list: {
|
||||
pagination: {
|
||||
hasMoreItems: false,
|
||||
maxItems: 25,
|
||||
skipCount: 0,
|
||||
totalItems: 0
|
||||
},
|
||||
entries: []
|
||||
}
|
||||
};
|
||||
|
||||
let errorJson = {
|
||||
error: {
|
||||
errorKey: 'Search failed',
|
||||
statusCode: 400,
|
||||
briefSummary: '08220082 search failed',
|
||||
stackTrace: 'For security reasons the stack trace is no longer displayed, but the property is kept for previous versions.',
|
||||
descriptionURL: 'https://api-explorer.alfresco.com'
|
||||
}
|
||||
};
|
||||
let fixture: ComponentFixture<SimpleSearchTestComponent>, element: HTMLElement;
|
||||
let component: SimpleSearchTestComponent;
|
||||
let searchService: SearchService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule,
|
||||
DocumentListModule
|
||||
SearchModule
|
||||
],
|
||||
declarations: [SearchComponent],
|
||||
providers: [
|
||||
SearchService,
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
{provide: NotificationService, useClass: NotificationService}
|
||||
]
|
||||
declarations: [ SimpleSearchTestComponent ]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(SearchComponent);
|
||||
fixture = TestBed.createComponent(SimpleSearchTestComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
searchService = TestBed.get(SearchService);
|
||||
});
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
describe('search results', () => {
|
||||
|
||||
it('should not have a search term by default', () => {
|
||||
expect(component.searchTerm).toBe('');
|
||||
});
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should take the provided search term from query param provided via RouteParams', () => {
|
||||
let injector = ReflectiveInjector.resolveAndCreate([
|
||||
{provide: ActivatedRoute, useValue: {params: Observable.from([{q: 'exampleTerm692'}])}}
|
||||
]);
|
||||
|
||||
let search = new SearchComponent(null, null, null, injector.get(ActivatedRoute));
|
||||
|
||||
search.ngOnInit();
|
||||
|
||||
expect(search.searchTerm).toBe('exampleTerm692');
|
||||
});
|
||||
|
||||
it('should show the Notification snackbar on permission error', () => {
|
||||
const notoficationService = TestBed.get(NotificationService);
|
||||
spyOn(notoficationService, 'openSnackMessage');
|
||||
|
||||
component.handlePermission(new PermissionModel());
|
||||
|
||||
expect(notoficationService.openSnackMessage).toHaveBeenCalledWith('PERMISSON.LACKOF', 3000);
|
||||
});
|
||||
|
||||
describe('Search results', () => {
|
||||
|
||||
it('should add wildcard in the search parameters', (done) => {
|
||||
let searchTerm = 'searchTerm6368';
|
||||
let searchTermOut = 'searchTerm6368*';
|
||||
let options = {
|
||||
include: ['path', 'allowableOperations'],
|
||||
skipCount: 0,
|
||||
rootNodeId: '-my-',
|
||||
nodeType: 'my:type',
|
||||
maxItems: 20,
|
||||
orderBy: null
|
||||
};
|
||||
|
||||
component.searchTerm = searchTerm;
|
||||
component.rootNodeId = '-my-';
|
||||
component.resultType = 'my:type';
|
||||
let searchService = fixture.debugElement.injector.get(SearchService);
|
||||
it('should clear results straight away when a new search term is entered', async(() => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
fixture.detectChanges();
|
||||
.and.returnValues(Promise.resolve(result), Promise.resolve(differentResult));
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
component.setSearchWordTo('searchTerm');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(searchService.getQueryNodesPromise).toHaveBeenCalledWith(searchTermOut, options);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should display search results when a search term is provided', (done) => {
|
||||
let searchService = TestBed.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise').and.returnValue(Promise.resolve(result));
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
component.resultsLoad.subscribe(() => {
|
||||
let optionShowed = element.querySelectorAll('#autocomplete-search-result-list > li').length;
|
||||
expect(optionShowed).toBe(1);
|
||||
component.setSearchWordTo('searchTerm2');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyDoc"]');
|
||||
expect(resultsEl).not.toBeNull();
|
||||
expect(resultsEl.innerHTML.trim()).toContain('MyDoc');
|
||||
done();
|
||||
optionShowed = element.querySelectorAll('#autocomplete-search-result-list > li').length;
|
||||
expect(optionShowed).toBe(1);
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
it('should display no result if no result are returned', (done) => {
|
||||
|
||||
let searchService = TestBed.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(noResult));
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('.no-result-message')).not.toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
|
||||
it('should display an error if an error is encountered running the search', (done) => {
|
||||
|
||||
let searchService = TestBed.get(SearchService);
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.reject(errorJson));
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
}, () => {
|
||||
fixture.detectChanges();
|
||||
let errorEl = element.querySelector('[data-automation-id="search_error_message"]');
|
||||
expect(errorEl).not.toBeNull();
|
||||
expect((<any> errorEl).innerText).toBe('SEARCH.RESULTS.ERROR');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update search results when the search term input is changed', (done) => {
|
||||
|
||||
let searchService = TestBed.get(SearchService);
|
||||
it('should display the returned search results', async(() => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.resolve(result));
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
component.setSearchWordTo('searchTerm');
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#result_option_0').textContent.trim()).toBe('MyDoc');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should emit error event when search call fail', async(() => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValue(Promise.reject({ status: 402 }));
|
||||
component.setSearchWordTo('searchTerm');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let message: HTMLElement = <HTMLElement> element.querySelector('#component-result-message');
|
||||
expect(message.textContent).toBe('ERROR');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should be able to hide the result panel', async(() => {
|
||||
spyOn(searchService, 'getQueryNodesPromise')
|
||||
.and.returnValues(Promise.resolve(result), Promise.resolve(differentResult));
|
||||
|
||||
component.setSearchWordTo('searchTerm');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
let optionShowed = element.querySelectorAll('#autocomplete-search-result-list');
|
||||
expect(optionShowed).not.toBeNull();
|
||||
component.forceHidePanel();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(searchService.getQueryNodesPromise).toHaveBeenCalled();
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyDoc"]');
|
||||
expect(resultsEl).not.toBeNull();
|
||||
expect(resultsEl.innerHTML.trim()).toContain('MyDoc');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm2', true)});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('search result interactions', () => {
|
||||
|
||||
let debugElement: DebugElement;
|
||||
let searchService: SearchService;
|
||||
let querySpy: jasmine.Spy;
|
||||
let emitSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
debugElement = fixture.debugElement;
|
||||
searchService = TestBed.get(SearchService);
|
||||
querySpy = spyOn(searchService, 'getQueryNodesPromise').and.returnValue(Promise.resolve(result));
|
||||
emitSpy = spyOn(component.preview, 'emit');
|
||||
});
|
||||
|
||||
describe('click results', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.navigationMode = SearchComponent.SINGLE_CLICK_NAVIGATION;
|
||||
});
|
||||
|
||||
it('should emit preview event when file item clicked', (done) => {
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyDoc"]');
|
||||
resultsEl.dispatchEvent(new Event('click'));
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
let elementList = element.querySelector('#adf-search-results-content');
|
||||
expect(elementList.classList).toContain('adf-search-hide');
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit preview event when non-file item is clicked', (done) => {
|
||||
querySpy.and.returnValue(Promise.resolve(folderResult));
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyFolder"]');
|
||||
resultsEl.dispatchEvent(new Event('click'));
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('double click results', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.navigationMode = SearchComponent.DOUBLE_CLICK_NAVIGATION;
|
||||
});
|
||||
|
||||
it('should emit preview event when file item clicked', (done) => {
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyDoc"]');
|
||||
resultsEl.dispatchEvent(new Event('dblclick'));
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit preview event when non-file item is clicked', (done) => {
|
||||
|
||||
querySpy.and.returnValue(Promise.resolve(folderResult));
|
||||
|
||||
component.searchTerm = '';
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
|
||||
component.resultsLoad.subscribe(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let resultsEl = element.querySelector('[data-automation-id="text_MyFolder"]');
|
||||
resultsEl.dispatchEvent(new Event('dblclick'));
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({searchTerm: new SimpleChange('', 'searchTerm', true)});
|
||||
});
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -15,27 +15,47 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { NodePaging, Pagination } from 'alfresco-js-api';
|
||||
import { AlfrescoTranslationService, NotificationService, SearchOptions, SearchService } from 'ng2-alfresco-core';
|
||||
import { PermissionModel } from 'ng2-alfresco-documentlist';
|
||||
|
||||
declare var require: any;
|
||||
import {
|
||||
AfterContentInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { NodePaging } from 'alfresco-js-api';
|
||||
import { SearchOptions, SearchService } from 'ng2-alfresco-core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search',
|
||||
styleUrls: ['./search.component.scss'],
|
||||
templateUrl: './search.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
styleUrls: ['./search.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
preserveWhitespaces: false,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
exportAs: 'searchAutocomplete',
|
||||
host: {
|
||||
'class': 'adf-search'
|
||||
}
|
||||
})
|
||||
export class SearchComponent implements OnChanges, OnInit {
|
||||
export class SearchComponent implements AfterContentInit, OnChanges {
|
||||
|
||||
static SINGLE_CLICK_NAVIGATION: string = 'click';
|
||||
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
|
||||
@ViewChild('panel')
|
||||
panel: ElementRef;
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: TemplateRef<any>;
|
||||
|
||||
@Input()
|
||||
searchTerm: string = '';
|
||||
displayWith: ((value: any) => string) | null = null;
|
||||
|
||||
@Input()
|
||||
maxResults: number = 20;
|
||||
@ -50,121 +70,117 @@ export class SearchComponent implements OnChanges, OnInit {
|
||||
resultType: string = null;
|
||||
|
||||
@Input()
|
||||
navigationMode: string = SearchComponent.DOUBLE_CLICK_NAVIGATION; // click|dblclick
|
||||
searchTerm: string = '';
|
||||
|
||||
@Input()
|
||||
navigate: boolean = true;
|
||||
|
||||
@Input()
|
||||
emptyFolderImageUrl: string = require('../assets/images/empty_doc_lib.svg');
|
||||
|
||||
@Output()
|
||||
resultsLoad = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
preview: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
nodeDbClick: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
pagination: Pagination;
|
||||
errorMessage;
|
||||
queryParamName = 'q';
|
||||
skipCount: number = 0;
|
||||
nodeResults: NodePaging;
|
||||
|
||||
constructor(private searchService: SearchService,
|
||||
private translateService: AlfrescoTranslationService,
|
||||
private notificationService: NotificationService,
|
||||
@Optional() private route: ActivatedRoute) {
|
||||
@Input('class')
|
||||
set classList(classList: string) {
|
||||
if (classList && classList.length) {
|
||||
classList.split(' ').forEach(className => this._classList[className.trim()] = true);
|
||||
this._elementRef.nativeElement.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.route) {
|
||||
this.route.params.forEach((params: Params) => {
|
||||
this.searchTerm = params.hasOwnProperty(this.queryParamName) ? params[this.queryParamName] : null;
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
@Output()
|
||||
resultLoaded: EventEmitter<NodePaging> = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
showPanel: boolean = false;
|
||||
results: NodePaging;
|
||||
|
||||
get isOpen(): boolean {
|
||||
return this._isOpen && this.showPanel;
|
||||
}
|
||||
|
||||
set isOpen(value: boolean) {
|
||||
this._isOpen = value;
|
||||
}
|
||||
|
||||
_isOpen: boolean = false;
|
||||
|
||||
keyPressedStream: Subject<string> = new Subject();
|
||||
|
||||
_classList: { [key: string]: boolean } = {};
|
||||
|
||||
constructor(
|
||||
private searchService: SearchService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef) {
|
||||
this.keyPressedStream.asObservable()
|
||||
.debounceTime(200)
|
||||
.subscribe((searchedWord: string) => {
|
||||
this.displaySearchResults(searchedWord);
|
||||
});
|
||||
} else {
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.setVisibility();
|
||||
}
|
||||
|
||||
ngOnChanges(changes) {
|
||||
if (changes.searchTerm) {
|
||||
this.resetResults();
|
||||
this.displaySearchResults(changes.searchTerm.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['searchTerm']) {
|
||||
this.searchTerm = changes['searchTerm'].currentValue;
|
||||
this.skipCount = 0;
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
resetResults() {
|
||||
this.cleanResults();
|
||||
this.setVisibility();
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
}
|
||||
|
||||
private cleanResults() {
|
||||
if (this.results) {
|
||||
this.results = {};
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleClick($event: any) {
|
||||
if (!this.navigate && $event.value) {
|
||||
this.nodeDbClick.emit({ value: $event.value });
|
||||
}
|
||||
}
|
||||
|
||||
onPreviewFile(event: any) {
|
||||
if (event.value) {
|
||||
this.preview.emit({ value: event.value });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and displays search results
|
||||
* @param searchTerm Search query entered by user
|
||||
*/
|
||||
private displaySearchResults(searchTerm) {
|
||||
if (searchTerm && this.searchService) {
|
||||
let searchOpts: SearchOptions = {
|
||||
include: ['path', 'allowableOperations'],
|
||||
rootNodeId: this.rootNodeId,
|
||||
nodeType: this.resultType,
|
||||
maxItems: this.maxResults,
|
||||
orderBy: this.resultSort
|
||||
};
|
||||
if (searchTerm !== null && searchTerm !== '') {
|
||||
searchTerm = searchTerm + '*';
|
||||
let searchOpts: SearchOptions = {
|
||||
include: ['path', 'allowableOperations'],
|
||||
skipCount: this.skipCount,
|
||||
rootNodeId: this.rootNodeId,
|
||||
nodeType: this.resultType,
|
||||
maxItems: this.maxResults,
|
||||
orderBy: this.resultSort
|
||||
};
|
||||
this.searchService
|
||||
.getNodeQueryResults(searchTerm, searchOpts)
|
||||
.subscribe(
|
||||
results => {
|
||||
this.nodeResults = results;
|
||||
this.pagination = results.list.pagination;
|
||||
this.resultsLoad.emit(results.list.entries);
|
||||
this.errorMessage = null;
|
||||
},
|
||||
error => {
|
||||
if (error.status !== 400) {
|
||||
this.errorMessage = <any> error;
|
||||
this.resultsLoad.error(error);
|
||||
}
|
||||
results => {
|
||||
this.results = <NodePaging> results;
|
||||
this.resultLoaded.emit(this.results);
|
||||
this.isOpen = true;
|
||||
this.setVisibility();
|
||||
},
|
||||
error => {
|
||||
if (error.status !== 400) {
|
||||
this.results = null;
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onChangePageSize(event: Pagination): void {
|
||||
this.maxResults = event.maxItems;
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
hidePanel() {
|
||||
if (this.isOpen) {
|
||||
this._classList['adf-search-show'] = false;
|
||||
this._classList['adf-search-hide'] = true;
|
||||
this.isOpen = false;
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
public onNextPage(event: Pagination): void {
|
||||
this.skipCount = event.skipCount;
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
}
|
||||
|
||||
public onPrevPage(event: Pagination): void {
|
||||
this.skipCount = event.skipCount;
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
}
|
||||
|
||||
public onContentDelete(entry: any) {
|
||||
this.displaySearchResults(this.searchTerm);
|
||||
}
|
||||
|
||||
public handlePermission(permission: PermissionModel): void {
|
||||
let permissionErrorMessage: any = this.translateService.get('PERMISSON.LACKOF', permission);
|
||||
this.notificationService.openSnackMessage(permissionErrorMessage.value, 3000);
|
||||
setVisibility() {
|
||||
this.showPanel = !!this.results && !!this.results.list;
|
||||
this._classList['adf-search-show'] = this.showPanel;
|
||||
this._classList['adf-search-hide'] = !this.showPanel;
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
@import '../src/components/search-autocomplete.component';
|
||||
@import '../src/components/search.component';
|
||||
@import '../src/components/search-control.component';
|
||||
|
||||
@mixin alfresco-search-theme($theme) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user