[ADF-2150] improved queryBody mechanism (#2852)

* [ADF-2150] changed query body parameter to a function

* [ADF-2150] added an example page where to try change query body

* [ADF-2150] improved queryBody mechanism

* [ADF-2150] fixed content node test

* [ADF-2150] extended docs added another way to use the query node

* [ADF-2150] fixed test for search on content node

* [ADF-2150] added some improvements to service config

* [ADF-2150] changed the documentation accordingly

* [ADF-2150] added PR changes

* [ADF-2150] fixed jdoc

* [ADF-2150] added checkbox to switch from service approach to input object approach

* [ADF-2150] fixed build error on demo shell
This commit is contained in:
Vito 2018-01-23 11:12:52 +01:00 committed by Eugenio Romano
parent 8788eaeb80
commit a401ebd35d
22 changed files with 430 additions and 78 deletions

View File

@ -25,7 +25,12 @@
"SOCIAL": "Social", "SOCIAL": "Social",
"SETTINGS": "Settings", "SETTINGS": "Settings",
"OVERLAY_VIEWER": "Overlay Viewer", "OVERLAY_VIEWER": "Overlay Viewer",
"ABOUT": "About" "ABOUT": "About",
"SEARCH": "Extended Search",
"EXTENDED_SEARCH_QUERY_BODY": "Extended Search with Query Body",
"WORD_TO_SEARCH":"Search Word",
"SEARCH_CREATED_BY" : "Created By",
"SEARCH_SERVICE_APPROACH": "Check this to disable the input property and use the service approach"
}, },
"DOCUMENT_LIST": { "DOCUMENT_LIST": {
"MULTISELECT_CHECKBOXES" :"Multiselect (with checkboxes)", "MULTISELECT_CHECKBOXES" :"Multiselect (with checkboxes)",

View File

@ -16,6 +16,7 @@ import { AppLayoutComponent } from './components/app-layout/app-layout.component
import { HomeComponent } from './components/home/home.component'; import { HomeComponent } from './components/home/home.component';
import { SearchBarComponent } from './components/search/search-bar.component'; import { SearchBarComponent } from './components/search/search-bar.component';
import { SearchResultComponent } from './components/search/search-result.component'; import { SearchResultComponent } from './components/search/search-result.component';
import { SearchExtendedComponent } from './components/search/search-extended.component';
import { AboutComponent } from './components/about/about.component'; import { AboutComponent } from './components/about/about.component';
import { FormComponent } from './components/form/form.component'; import { FormComponent } from './components/form/form.component';
import { FormListComponent } from './components/form/form-list.component'; import { FormListComponent } from './components/form/form-list.component';
@ -68,6 +69,7 @@ import { SharedLinkViewComponent } from './components/shared-link-view/shared-li
HomeComponent, HomeComponent,
SearchBarComponent, SearchBarComponent,
SearchResultComponent, SearchResultComponent,
SearchExtendedComponent,
AboutComponent, AboutComponent,
ProcessServiceComponent, ProcessServiceComponent,
ShowDiagramComponent, ShowDiagramComponent,

View File

@ -29,6 +29,7 @@ import { FormViewerComponent } from './components/process-service/form-viewer.co
import { FormNodeViewerComponent } from './components/process-service/form-node-viewer.component'; import { FormNodeViewerComponent } from './components/process-service/form-node-viewer.component';
import { AppsViewComponent } from './components/process-service/apps-view.component'; import { AppsViewComponent } from './components/process-service/apps-view.component';
import { SearchResultComponent } from './components/search/search-result.component'; import { SearchResultComponent } from './components/search/search-result.component';
import { SearchExtendedComponent } from './components/search/search-extended.component';
import { DataTableComponent } from './components/datatable/datatable.component'; import { DataTableComponent } from './components/datatable/datatable.component';
import { WebscriptComponent } from './components/webscript/webscript.component'; import { WebscriptComponent } from './components/webscript/webscript.component';
@ -95,6 +96,11 @@ export const appRoutes: Routes = [
component: SearchResultComponent, component: SearchResultComponent,
canActivate: [AuthGuardEcm] canActivate: [AuthGuardEcm]
}, },
{
path: 'extendedSearch',
component: SearchExtendedComponent,
canActivate: [AuthGuardEcm]
},
{ {
path: 'activiti', path: 'activiti',
component: AppsViewComponent, component: AppsViewComponent,

View File

@ -41,6 +41,7 @@ export class AppLayoutComponent {
{ href: '/tag', icon: 'local_offer', title: 'APP_LAYOUT.TAG' }, { href: '/tag', icon: 'local_offer', title: 'APP_LAYOUT.TAG' },
{ href: '/social', icon: 'thumb_up', title: 'APP_LAYOUT.SOCIAL' }, { href: '/social', icon: 'thumb_up', title: 'APP_LAYOUT.SOCIAL' },
{ href: '/settings', icon: 'settings', title: 'APP_LAYOUT.SETTINGS' }, { href: '/settings', icon: 'settings', title: 'APP_LAYOUT.SETTINGS' },
{ href: '/extendedSearch', icon: 'search', title: 'APP_LAYOUT.SEARCH' },
{ href: '/overlay-viewer', icon: 'pageview', title: 'APP_LAYOUT.OVERLAY_VIEWER' }, { href: '/overlay-viewer', icon: 'pageview', title: 'APP_LAYOUT.OVERLAY_VIEWER' },
{ href: '/about', icon: 'info_outline', title: 'APP_LAYOUT.ABOUT' } { href: '/about', icon: 'info_outline', title: 'APP_LAYOUT.ABOUT' }
]; ];

View File

@ -0,0 +1,44 @@
/*!
* @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 { QueryBody } from 'alfresco-js-api';
import { SearchConfigurationInterface } from '@alfresco/adf-core';
export class TestSearchConfigurationService implements SearchConfigurationInterface {
constructor() {
}
public generateQueryBody(searchTerm: string, maxResults: string, skipCount: string): QueryBody {
const defaultQueryBody: QueryBody = {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
paging: {
maxItems: maxResults,
skipCount: skipCount
},
filterQueries: [
/*tslint:disable-next-line */
{ query: "TYPE:'cm:folder'" },
{ query: 'NOT cm:creator:System' }]
};
return defaultQueryBody;
}
}

View File

@ -0,0 +1,39 @@
<div>
<h3>{{'APP_LAYOUT.EXTENDED_SEARCH_QUERY_BODY' | translate}}</h3>
<mat-checkbox [(ngModel)]="useServiceApproach">
{{'APP_LAYOUT.SEARCH_SERVICE_APPROACH' | translate}}
</mat-checkbox>
<div id="container-for-custom-input" class="search-extended-input-containers">
<mat-form-field>
<label>{{'APP_LAYOUT.WORD_TO_SEARCH' | translate}}</label>
<input type="text" matInput
id="custom-input" [(ngModel)]="searchedWord" [searchAutocomplete]="auto">
</mat-form-field>
</div>
<div>
<adf-search #auto="searchAutocomplete"
[queryBody]="generateQueryBody(searchedWord)"
class="example-card-search-container">
<ng-template let-data>
<mat-card class="example-card"
*ngFor="let item of data?.list?.entries; let idx = index" (click)="onClick(item)">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>{{ item?.entry.name }}</mat-card-title>
<mat-card-subtitle>{{ item?.entry.createdAt }}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image [src]="getMimeTypeIcon(item)">
<mat-card-content>
<p>
{{'APP_LAYOUT.SEARCH_CREATED_BY' | translate}}: {{item?.entry.createdByUser?.displayName}}
</p>
</mat-card-content>
</mat-card>
<mat-card class="example-card" 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-card>
</ng-template>
</adf-search>
</div>
</div>

View File

@ -0,0 +1,62 @@
div.search-results-container {
padding: 0 20px 20px 20px;
}
.adf-search-title {
font-size: 22px;
padding: 15px 0 15px 0;
}
@media screen and (max-width: 600px) {
:host .col-display-name {
min-width: 100px;
}
:host .col-modified-at, :host .col-modified-by {
display: none;
}
:host div.search-results-container table {
width: 100%;
}
}
.adf-search-results-content{
display: flex;
}
.search-extended-input-containers {
display: flex;
flex-direction: row-reverse;
justify-content: space-evenly;
}
.search-extended-input-textarea {
width: 300px;
}
.search-extended-label-error {
display: flex;
flex-direction: column;
}
.example-card {
width: 200px;
flex: 0 20%;
margin: 15px;
}
.example-card-search-container {
display: flex;
flex-wrap: wrap;
}
.example-header-image {
background-size: cover;
margin-bottom: 15px;
}
.example-search-input {
width: 100px;
border: 1 solid;
border-color: black;
}

View File

@ -0,0 +1,84 @@
/*!
* @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, ViewChild, ViewEncapsulation } from '@angular/core';
import { NodePaging, Pagination, QueryBody, MinimalNodeEntity } from 'alfresco-js-api';
import { SearchComponent } from '@alfresco/adf-content-services';
import { ThumbnailService } from '@alfresco/adf-core';
import { SearchService, SearchConfigurationService } from '@alfresco/adf-core';
import { TestSearchConfigurationService } from './search-config-test.service';
@Component({
selector: 'app-search-extended-component',
templateUrl: './search-extended.component.html',
styleUrls: ['./search-extended.component.scss'],
encapsulation: ViewEncapsulation.None,
providers: [
{ provide: SearchConfigurationService, useClass: TestSearchConfigurationService },
SearchService
]
})
export class SearchExtendedComponent {
@ViewChild('search')
search: SearchComponent;
queryParamName = 'q';
searchedWord = '';
queryBodyString = '';
errorMessage = '';
resultNodePageList: NodePaging;
maxItems: number;
skipCount = 0;
pagination: Pagination;
queryBody: QueryBody;
useServiceApproach = false;
constructor(public thumbnailService: ThumbnailService) {
}
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);
}
generateQueryBody(searchTerm: string): QueryBody {
if (this.useServiceApproach) {
return null;
} else {
return {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
filterQueries: [
/*tslint:disable-next-line */
{ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ query: 'NOT cm:creator:System' }]
};
}
}
}

View File

@ -25,7 +25,8 @@ Displays a input text which shows find-as-you-type suggestions.
| expandable | boolean | true | Whether to use an expanding search control, if false then a regular input is used. | | expandable | boolean | true | Whether to use an expanding search control, if false then a regular input is used. |
| liveSearchEnabled | boolean | true | Whether find-as-you-type suggestions should be offered for matching content items. Set to false to disable. | | liveSearchEnabled | boolean | true | Whether find-as-you-type suggestions should be offered for matching content items. Set to false to disable. |
| liveSearchMaxResults | number | 5 | Maximum number of results to show in the live search. | | liveSearchMaxResults | number | 5 | Maximum number of results to show in the live search. |
| customQueryBody | [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api | | customQueryBody | [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api. This input is deprecated, to use the extended query body function please refer to the suggested solution [here](./search.component.md#querybody)|
### Events ### Events

View File

@ -19,7 +19,7 @@ Searches items for supplied search terms.
| maxResults | number | 20 | Maximum number of results to show in the search. | | maxResults | number | 20 | Maximum number of results to show in the search. |
| skipResults | number | 0 | Number of results to skip from the results pagination. | | skipResults | number | 0 | Number of results to skip from the results pagination. |
| displayWith | function | | Function that maps an option's value to its display value in the trigger | | displayWith | function | | Function that maps an option's value to its display value in the trigger |
| queryBody | [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api | | queryBody| [QueryBody](https://github.com/Alfresco/alfresco-js-api/blob/1.6.0/src/alfresco-search-rest-api/docs/QueryBody.md) | | object which allow you to perform more elaborated query from the search api. This input is deprecated, to use the extended query body function please refer to the suggested solution [here](./search.component.md#querybody) |
### Events ### Events
@ -113,3 +113,60 @@ Yuo can do this by exporting the adf-search panel instance into a local template
``` ```
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. 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.
## QueryBody
This is an example on how you can provide your own class to generate your custom query body without giving it in input to the search component.
1. Service Class
You need to create your own service class which will implement the SearchConfigurationInterface this will force you to create the method generateQueryBody that is the one which needs to return the QueryBody object.
```ts
import { QueryBody } from 'alfresco-js-api';
import { SearchConfigurationInterface } from '@alfresco/adf-core';
export class TestSearchConfigurationService implements SearchConfigurationInterface {
constructor() {
}
public generateQueryBody(searchTerm: string, maxResults: string, skipCount: string): QueryBody {
const defaultQueryBody: QueryBody = {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
paging: {
maxItems: maxResults,
skipCount: skipCount
},
filterQueries: [
{ query: "TYPE:'cm:folder'" },
{ query: 'NOT cm:creator:System' }]
};
return defaultQueryBody;
}
}
```
2. Provide your service class to the module
Once you have created your service class to provide your custom query body you need to inform the component to use your class instead of the default one. This can be easily achieved via your component providers :
```ts
import { SearchService, SearchConfigurationService } from '@alfresco/adf-core';
import { TestSearchConfigurationService } from './search-config-test.service';
@Component({
selector: 'app-search-extended-component',
templateUrl: './search-extended.component.html',
styleUrls: ['./search-extended.component.scss'],
encapsulation: ViewEncapsulation.None,
providers: [
{ provide: SearchConfigurationService, useClass: TestSearchConfigurationService },
SearchService
]
})
```
You need to add as provider even the SearchService to avoid the override of the module instance. So this component will have his own instance of the SearchService that will use as configuration the class you have provided.

View File

@ -301,7 +301,7 @@ describe('ContentNodeSelectorComponent', () => {
typeToSearchBox('kakarot'); typeToSearchBox('kakarot');
setTimeout(() => { setTimeout(() => {
expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot')); expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot'), '25', '0');
done(); done();
}, 300); }, 300);
}); });
@ -325,7 +325,7 @@ describe('ContentNodeSelectorComponent', () => {
component.siteChanged(<SiteEntry> { entry: { guid: 'namek' } }); component.siteChanged(<SiteEntry> { entry: { guid: 'namek' } });
expect(searchSpy.calls.count()).toBe(2, 'Search count should be two after the site change'); expect(searchSpy.calls.count()).toBe(2, 'Search count should be two after the site change');
expect(searchSpy.calls.argsFor(1)).toEqual([defaultSearchOptions('vegeta', 'namek')]); expect(searchSpy.calls.argsFor(1)).toEqual([defaultSearchOptions('vegeta', 'namek'), '25', '0'] );
done(); done();
}, 300); }, 300);
}); });
@ -523,7 +523,7 @@ describe('ContentNodeSelectorComponent', () => {
component.getNextPageOfSearch({ skipCount }); component.getNextPageOfSearch({ skipCount });
expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot', undefined, skipCount)); expect(searchSpy).toHaveBeenCalledWith(defaultSearchOptions('kakarot', undefined, skipCount), '25', skipCount.toString());
}); });
it('should be shown when pagination\'s hasMoreItems is true', () => { it('should be shown when pagination\'s hasMoreItems is true', () => {

View File

@ -59,6 +59,6 @@ export class ContentNodeSelectorService {
} }
}; };
return this.searchService.search(defaultSearchNode); return this.searchService.search(defaultSearchNode, maxItems.toString(), skipCount.toString());
} }
} }

View File

@ -27,8 +27,8 @@
<adf-search #auto="searchAutocomplete" <adf-search #auto="searchAutocomplete"
class="adf-search-result-autocomplete" class="adf-search-result-autocomplete"
[queryBody]="customQueryBody" [maxResults]="liveSearchMaxResults"
[maxResults]="liveSearchMaxResults"> [queryBody]="customQueryBody">
<ng-template let-data> <ng-template let-data>
<mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list"> <mat-list *ngIf="isSearchBarActive()" id="autocomplete-search-result-list">
<mat-list-item <mat-list-item

View File

@ -63,6 +63,7 @@ export class SearchControlComponent implements OnInit, OnDestroy {
@Input() @Input()
liveSearchMaxResults: number = 5; liveSearchMaxResults: number = 5;
/** @deprecated in 2.1.0 */
@Input() @Input()
customQueryBody: QueryBody; customQueryBody: QueryBody;

View File

@ -23,10 +23,10 @@ import { SearchModule } from '../../index';
import { differentResult, folderResult, result, SimpleSearchTestComponent } from '../../mock'; import { differentResult, folderResult, result, SimpleSearchTestComponent } from '../../mock';
function fakeNodeResultSearch(searchNode: QueryBody): Observable<any> { function fakeNodeResultSearch(searchNode: QueryBody): Observable<any> {
if (searchNode.query.query === 'FAKE_SEARCH_EXMPL') { if (searchNode && searchNode.query.query === 'FAKE_SEARCH_EXMPL') {
return Observable.of(differentResult); return Observable.of(differentResult);
} }
if (searchNode.filterQueries.length === 1 && if (searchNode && searchNode.filterQueries.length === 1 &&
searchNode.filterQueries[0].query === "TYPE:'cm:folder'") { searchNode.filterQueries[0].query === "TYPE:'cm:folder'") {
return Observable.of(folderResult); return Observable.of(folderResult);
} }
@ -132,18 +132,18 @@ describe('SearchComponent', () => {
}); });
it('should perform a search based on the query node given', async(() => { it('should perform a search based on the query node given', async(() => {
spyOn(searchService, 'search') spyOn(searchService, 'searchByQueryBody')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj)); .and.callFake((searchObj) => fakeNodeResultSearch(searchObj));
let fakeSearchNode: QueryBody = { let fakeSearchNode: QueryBody = {
query: { query: {
query: '' query: 'TEST-FAKE-NODE'
}, },
filterQueries: [ filterQueries: [
{ 'query': "TYPE:'cm:folder'" } { 'query': "TYPE:'cm:folder'" }
] ]
}; };
component.setSearchNodeTo(fakeSearchNode);
component.setSearchWordTo('searchTerm'); component.setSearchWordTo('searchTerm');
component.setSearchNodeTo(fakeSearchNode);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@ -156,7 +156,7 @@ describe('SearchComponent', () => {
it('should perform a search with a defaultNode if no searchnode is given', async(() => { it('should perform a search with a defaultNode if no searchnode is given', async(() => {
spyOn(searchService, 'search') spyOn(searchService, 'search')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj)); .and.returnValue(Observable.of(result));
component.setSearchWordTo('searchTerm'); component.setSearchWordTo('searchTerm');
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
@ -169,7 +169,7 @@ describe('SearchComponent', () => {
})); }));
it('should perform a search with the searchNode given', async(() => { it('should perform a search with the searchNode given', async(() => {
spyOn(searchService, 'search') spyOn(searchService, 'searchByQueryBody')
.and.callFake((searchObj) => fakeNodeResultSearch(searchObj)); .and.callFake((searchObj) => fakeNodeResultSearch(searchObj));
let fakeSearchNode: QueryBody = { let fakeSearchNode: QueryBody = {
query: { query: {
@ -179,6 +179,7 @@ describe('SearchComponent', () => {
{ 'query': "TYPE:'cm:folder'" } { 'query': "TYPE:'cm:folder'" }
] ]
}; };
component.setSearchWordTo('searchTerm');
component.setSearchNodeTo(fakeSearchNode); component.setSearchNodeTo(fakeSearchNode);
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {

View File

@ -18,8 +18,6 @@
import { SearchService } from '@alfresco/adf-core'; import { SearchService } from '@alfresco/adf-core';
import { import {
AfterContentInit, AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, Component,
ContentChild, ContentChild,
ElementRef, ElementRef,
@ -40,7 +38,6 @@ import { Subject } from 'rxjs/Subject';
styleUrls: ['./search.component.scss'], styleUrls: ['./search.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
preserveWhitespaces: false, preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'searchAutocomplete', exportAs: 'searchAutocomplete',
host: { host: {
'class': 'adf-search' 'class': 'adf-search'
@ -63,12 +60,13 @@ export class SearchComponent implements AfterContentInit, OnChanges {
@Input() @Input()
skipResults: number = 0; skipResults: number = 0;
@Input() /** @deprecated in 2.1.0 */
searchTerm: string = '';
@Input() @Input()
queryBody: QueryBody; queryBody: QueryBody;
@Input()
searchTerm: string = '';
@Input('class') @Input('class')
set classList(classList: string) { set classList(classList: string) {
if (classList && classList.length) { if (classList && classList.length) {
@ -101,13 +99,13 @@ export class SearchComponent implements AfterContentInit, OnChanges {
_classList: { [key: string]: boolean } = {}; _classList: { [key: string]: boolean } = {};
constructor(private searchService: SearchService, constructor(private searchService: SearchService,
private changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef) { private _elementRef: ElementRef) {
this.keyPressedStream.asObservable() this.keyPressedStream.asObservable()
.debounceTime(200) .debounceTime(200)
.subscribe((searchedWord: string) => { .subscribe((searchedWord: string) => {
this.loadSearchResults(searchedWord); this.loadSearchResults(searchedWord);
}); });
} }
ngAfterContentInit() { ngAfterContentInit() {
@ -115,14 +113,12 @@ export class SearchComponent implements AfterContentInit, OnChanges {
} }
ngOnChanges(changes) { ngOnChanges(changes) {
this.resetResults(); if (changes.queryBody &&
this.hasDifferentQueryBody(changes.queryBody.previousValue, changes.queryBody.currentValue)) {
this.loadSearchResults();
}
if (changes.searchTerm && changes.searchTerm.currentValue) { if (changes.searchTerm && changes.searchTerm.currentValue) {
this.loadSearchResults(changes.searchTerm.currentValue); this.loadSearchResults(changes.searchTerm.currentValue);
} else if (changes.queryBody && changes.queryBody.currentValue) {
this.loadSearchResults();
} else {
this.loadSearchResults(this.searchTerm);
} }
} }
@ -135,23 +131,27 @@ export class SearchComponent implements AfterContentInit, OnChanges {
this.loadSearchResults(this.searchTerm); this.loadSearchResults(this.searchTerm);
} }
private hasDifferentQueryBody(previousQueryBody: QueryBody, currentQueryBody: QueryBody) {
return JSON.stringify(previousQueryBody) !== JSON.stringify(currentQueryBody);
}
private cleanResults() { private cleanResults() {
if (this.results) { if (this.results) {
this.results = {}; this.results = {};
} }
} }
private hasValidSearchQuery(searchOpts: QueryBody) {
return searchOpts && searchOpts.query && searchOpts.query.query;
}
private loadSearchResults(searchTerm?: string) { private loadSearchResults(searchTerm?: string) {
let searchOpts: QueryBody = this.getQueryBody(searchTerm); this.resetResults();
if (searchTerm) {
if (this.hasValidSearchQuery(searchOpts)) { let search$;
this.searchService if (this.queryBody) {
.search(searchOpts) search$ = this.searchService.searchByQueryBody(this.queryBody);
.subscribe( } else {
search$ = this.searchService
.search(searchTerm, this.maxResults.toString(), this.skipResults.toString());
}
search$.subscribe(
results => { results => {
this.results = <NodePaging> results; this.results = <NodePaging> results;
this.resultLoaded.emit(this.results); this.resultLoaded.emit(this.results);
@ -169,41 +169,11 @@ export class SearchComponent implements AfterContentInit, OnChanges {
} }
} }
private getQueryBody(searchTerm: string): QueryBody {
if (this.queryBody) {
if (!this.queryBody.query.query && searchTerm) {
this.queryBody.query.query = searchTerm;
}
return this.queryBody;
} else {
return this.generateDefaultSearchNode(searchTerm);
}
}
private generateDefaultSearchNode(searchTerm: string): QueryBody {
let defaultQueryBody: QueryBody = {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
paging: {
maxItems: this.maxResults.toString(),
skipCount: this.skipResults.toString()
},
filterQueries: [
{ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ query: 'NOT cm:creator:System' }]
};
return defaultQueryBody;
}
hidePanel() { hidePanel() {
if (this.isOpen) { if (this.isOpen) {
this._classList['adf-search-show'] = false; this._classList['adf-search-show'] = false;
this._classList['adf-search-hide'] = true; this._classList['adf-search-hide'] = true;
this.isOpen = false; this.isOpen = false;
this.changeDetectorRef.markForCheck();
} }
} }
@ -211,6 +181,5 @@ export class SearchComponent implements AfterContentInit, OnChanges {
this.showPanel = !!this.results && !!this.results.list; this.showPanel = !!this.results && !!this.results.list;
this._classList['adf-search-show'] = this.showPanel; this._classList['adf-search-show'] = this.showPanel;
this._classList['adf-search-hide'] = !this.showPanel; this._classList['adf-search-hide'] = !this.showPanel;
this.changeDetectorRef.markForCheck();
} }
} }

View File

@ -17,3 +17,4 @@
export * from './authentication.interface'; export * from './authentication.interface';
export * from './injection.tokens'; export * from './injection.tokens';
export * from './search-configuration.interface';

View File

@ -0,0 +1,24 @@
/*!
* @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 { QueryBody } from 'alfresco-js-api';
export interface SearchConfigurationInterface {
generateQueryBody(searchTerm: string, maxResults: string, skipCount: string): QueryBody;
}

View File

@ -46,3 +46,4 @@ export * from './shared-links-api.service';
export * from './sites.service'; export * from './sites.service';
export * from './discovery-api.service'; export * from './discovery-api.service';
export * from './comment-process.service'; export * from './comment-process.service';
export * from './search-configuration.service';

View File

@ -0,0 +1,45 @@
/*!
* @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 { Injectable } from '@angular/core';
import { QueryBody } from 'alfresco-js-api';
import { SearchConfigurationInterface } from '../interface/search-configuration.interface';
@Injectable()
export class SearchConfigurationService implements SearchConfigurationInterface {
constructor() {
}
public generateQueryBody(searchTerm: string, maxResults: string, skipCount: string): QueryBody {
let defaultQueryBody: QueryBody = {
query: {
query: searchTerm ? `${searchTerm}* OR name:${searchTerm}*` : searchTerm
},
include: ['path', 'allowableOperations'],
paging: {
maxItems: maxResults,
skipCount: skipCount
},
filterQueries: [
{ query: "TYPE:'cm:folder' OR TYPE:'cm:content'" },
{ query: 'NOT cm:creator:System' }]
};
return defaultQueryBody;
}
}

View File

@ -21,15 +21,14 @@ import { Observable } from 'rxjs/Observable';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { AuthenticationService } from './authentication.service'; import { AuthenticationService } from './authentication.service';
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw';
import { SearchConfigurationService } from './search-configuration.service';
/**
* Internal service used by Document List component.
*/
@Injectable() @Injectable()
export class SearchService { export class SearchService {
constructor(public authService: AuthenticationService, constructor(public authService: AuthenticationService,
private apiService: AlfrescoApiService) { private apiService: AlfrescoApiService,
private searchConfigurationService: SearchConfigurationService) {
} }
getNodeQueryResults(term: string, options?: SearchOptions): Observable<NodePaging> { getNodeQueryResults(term: string, options?: SearchOptions): Observable<NodePaging> {
@ -38,8 +37,8 @@ export class SearchService {
.catch(err => this.handleError(err)); .catch(err => this.handleError(err));
} }
search(query: QueryBody): Observable<NodePaging> { search(searchTerm: string, maxResults: string, skipCount: string): Observable<NodePaging> {
const searchQuery = Object.assign(query); const searchQuery = Object.assign(this.searchConfigurationService.generateQueryBody(searchTerm, maxResults, skipCount));
const promise = this.apiService.getInstance().search.searchApi.search(searchQuery); const promise = this.apiService.getInstance().search.searchApi.search(searchQuery);
return Observable return Observable
@ -47,6 +46,14 @@ export class SearchService {
.catch(err => this.handleError(err)); .catch(err => this.handleError(err));
} }
searchByQueryBody(queryBody: QueryBody): Observable<NodePaging> {
const promise = this.apiService.getInstance().search.searchApi.search(queryBody);
return Observable
.fromPromise(promise)
.catch(err => this.handleError(err));
}
private handleError(error: any): Observable<any> { private handleError(error: any): Observable<any> {
return Observable.throw(error || 'Server error'); return Observable.throw(error || 'Server error');
} }

View File

@ -47,6 +47,7 @@ import { TranslateLoaderService } from './translate-loader.service';
import { TranslationService } from './translation.service'; import { TranslationService } from './translation.service';
import { UploadService } from './upload.service'; import { UploadService } from './upload.service';
import { UserPreferencesService } from './user-preferences.service'; import { UserPreferencesService } from './user-preferences.service';
import { SearchConfigurationService } from './search-configuration.service';
@NgModule({ @NgModule({
imports: [], imports: [],
@ -81,7 +82,8 @@ import { UserPreferencesService } from './user-preferences.service';
SharedLinksApiService, SharedLinksApiService,
SitesService, SitesService,
DiscoveryApiService, DiscoveryApiService,
CommentProcessService CommentProcessService,
SearchConfigurationService
], ],
exports: [ exports: [
] ]