small fixes for PR

This commit is contained in:
lotte
2018-08-03 17:39:47 +02:00
parent 11f4befceb
commit e7ea6294d7
13 changed files with 135 additions and 29 deletions

View File

@@ -87,7 +87,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => { const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => {
return { options, page } return { options, page }
}).switchMap(({ options, page }) => { }).switchMap(({ options, page }) => {
return this.searchService.getFacetValuesFor(this.filterConfig, page, options).map((results) => { return this.searchService.getFacetValuesFor(this.filterConfig, page, options)
.first((RD) => !RD.isLoading).map((results) => {
return { return {
values: Observable.of(results), values: Observable.of(results),
page: page page: page

View File

@@ -1,7 +1,7 @@
<h3>{{"search.filters.head" | translate}}</h3> <h3>{{"search.filters.head" | translate}}</h3>
<div *ngIf="(filters | async)?.hasSucceeded"> <div *ngIf="(filters | async)?.hasSucceeded">
<div *ngFor="let filter of (filters | async)?.payload"> <div *ngFor="let filter of (filters | async)?.payload">
<ds-search-filter class="d-block mb-3 p-3" [filter]="filter"></ds-search-filter> <ds-search-filter *ngIf="isActive(filter) | async" class="d-block mb-3 p-3" [filter]="filter"></ds-search-filter>
</div> </div>
</div> </div>
<a class="btn btn-primary" [routerLink]="[getSearchLink()]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button">{{"search.filters.reset" | translate}}</a> <a class="btn btn-primary" [routerLink]="[getSearchLink()]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button">{{"search.filters.reset" | translate}}</a>

View File

@@ -24,6 +24,12 @@ describe('SearchFiltersComponent', () => {
} }
/* tslint:enable:no-empty */ /* tslint:enable:no-empty */
}; };
const searchFiltersStub = {
getSelectedValuesForFilter: (filter) =>
[]
};
const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', { const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', {
getCurrentFrontendFilters: Observable.of({}) getCurrentFrontendFilters: Observable.of({})
}); });
@@ -35,6 +41,7 @@ describe('SearchFiltersComponent', () => {
providers: [ providers: [
{ provide: SearchService, useValue: searchServiceStub }, { provide: SearchService, useValue: searchServiceStub },
{ provide: SearchConfigurationService, useValue: searchConfigServiceStub }, { provide: SearchConfigurationService, useValue: searchConfigServiceStub },
{ provide: SearchFilterService, useValue: searchFiltersStub },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -4,6 +4,8 @@ import { RemoteData } from '../../core/data/remote-data';
import { SearchFilterConfig } from '../search-service/search-filter-config.model'; import { SearchFilterConfig } from '../search-service/search-filter-config.model';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { SearchConfigurationService } from '../search-service/search-configuration.service'; import { SearchConfigurationService } from '../search-service/search-configuration.service';
import { isNotEmpty } from '../../shared/empty.util';
import { SearchFilterService } from './search-filter/search-filter.service';
@Component({ @Component({
selector: 'ds-search-filters', selector: 'ds-search-filters',
@@ -30,10 +32,14 @@ export class SearchFiltersComponent {
* Initialize instance variables * Initialize instance variables
* @param {SearchService} searchService * @param {SearchService} searchService
* @param {SearchConfigurationService} searchConfigService * @param {SearchConfigurationService} searchConfigService
* @param {SearchFilterService} filterService
*/ */
constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService) { constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) {
this.filters = searchService.getConfig(); this.filters = searchService.getConfig().first((RD) => !RD.isLoading);
this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => {
Object.keys(filters).forEach((f) => filters[f] = null);
return filters;
});
} }
/** /**
@@ -42,4 +48,29 @@ export class SearchFiltersComponent {
getSearchLink() { getSearchLink() {
return this.searchService.getSearchLink(); return this.searchService.getSearchLink();
} }
/**
* Check if a given filter is supposed to be shown or not
* @param {SearchFilterConfig} filter The filter to check for
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
*/
isActive(filter: SearchFilterConfig): Observable<boolean> {
// console.log(filter.name);
return this.filterService.getSelectedValuesForFilter(filter)
.flatMap((isActive) => {
if (isNotEmpty(isActive)) {
return Observable.of(true);
} else {
return this.searchConfigService.searchOptions
.switchMap((options) => {
return this.searchService.getFacetValuesFor(filter, 1, options)
.filter((RD) => !RD.isLoading)
.map((valuesRD) => {
return valuesRD.payload.totalElements > 0
})
}
)
}
}).startWith(true);
}
} }

View File

@@ -1,12 +1,13 @@
<div class="row"> <div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3"> <div class="labels col-sm-9 offset-sm-3">
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)"> <ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)"><!--Do not remove this to prevent uneven spacing
<a *ngFor="let values of (appliedFilters | async)[key]" class="badge badge-primary mr-1" --><a *ngFor="let values of (appliedFilters | async)[key]"
class="badge badge-primary mr-1 mb-1"
[routerLink]="getSearchLink()" [routerLink]="getSearchLink()"
[queryParams]="(getRemoveParams(key, values) | async)" queryParamsHandling="merge"> [queryParams]="(getRemoveParams(key, values) | async)" queryParamsHandling="merge">
{{('search.filters.applied.' + key) | translate}}: {{values}} {{('search.filters.applied.' + key) | translate}}: {{values}}
<span> ×</span> <span> ×</span>
</a> </a><!--Do not remove this to prevent uneven spacing
</ng-container> --></ng-container>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,3 @@
:host {
line-height: 1;
}

View File

@@ -8,6 +8,7 @@ import { SearchConfigurationService } from '../search-service/search-configurati
@Component({ @Component({
selector: 'ds-search-labels', selector: 'ds-search-labels',
styleUrls: ['./search-labels.component.scss'],
templateUrl: './search-labels.component.html', templateUrl: './search-labels.component.html',
}) })

View File

@@ -16,24 +16,55 @@ import { Subscription } from 'rxjs/Subscription';
*/ */
@Injectable() @Injectable()
export class SearchConfigurationService implements OnDestroy { export class SearchConfigurationService implements OnDestroy {
/**
* Default pagination settings
*/
private defaultPagination = Object.assign(new PaginationComponentOptions(), { private defaultPagination = Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration', id: 'search-page-configuration',
pageSize: 10, pageSize: 10,
currentPage: 1 currentPage: 1
}); });
/**
* Default sort settings
*/
private defaultSort = new SortOptions('score', SortDirection.DESC); private defaultSort = new SortOptions('score', SortDirection.DESC);
/**
* Default scope setting
*/
private defaultScope = ''; private defaultScope = '';
/**
* Default query setting
*/
private defaultQuery = ''; private defaultQuery = '';
private _defaults; /**
* Emits the current default values
*/
private _defaults: Observable<RemoteData<PaginatedSearchOptions>>;
/**
* Emits the current search options
*/
public searchOptions: BehaviorSubject<SearchOptions>; public searchOptions: BehaviorSubject<SearchOptions>;
/**
* Emits the current search options including pagination and sort
*/
public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>; public paginatedSearchOptions: BehaviorSubject<PaginatedSearchOptions>;
/**
* List of subscriptions to unsubscribe from on destroy
*/
private subs: Subscription[] = new Array(); private subs: Subscription[] = new Array();
/**
* Initialize the search options
* @param {RouteService} routeService
* @param {ActivatedRoute} route
*/
constructor(private routeService: RouteService, constructor(private routeService: RouteService,
private route: ActivatedRoute) { private route: ActivatedRoute) {
this.defaults.first().subscribe((defRD) => { this.defaults.first().subscribe((defRD) => {
@@ -128,6 +159,11 @@ export class SearchConfigurationService implements OnDestroy {
return this.routeService.getQueryParamsWithPrefix('f.'); return this.routeService.getQueryParamsWithPrefix('f.');
} }
/**
* Sets up a subscription to all necessary parameters to make sure the searchOptions emits a new value every time they update
* @param {SearchOptions} defaults Default values for when no parameters are available
* @returns {Subscription} The subscription to unsubscribe from
*/
subscribeToSearchOptions(defaults: SearchOptions): Subscription { subscribeToSearchOptions(defaults: SearchOptions): Subscription {
return Observable.merge( return Observable.merge(
this.getScopePart(defaults.scope), this.getScopePart(defaults.scope),
@@ -140,6 +176,11 @@ export class SearchConfigurationService implements OnDestroy {
}); });
} }
/**
* Sets up a subscription to all necessary parameters to make sure the paginatedSearchOptions emits a new value every time they update
* @param {PaginatedSearchOptions} defaults Default values for when no parameters are available
* @returns {Subscription} The subscription to unsubscribe from
*/
subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription { subscribeToPaginatedSearchOptions(defaults: PaginatedSearchOptions): Subscription {
return Observable.merge( return Observable.merge(
this.getPaginationPart(defaults.pagination), this.getPaginationPart(defaults.pagination),
@@ -170,12 +211,18 @@ export class SearchConfigurationService implements OnDestroy {
return this._defaults; return this._defaults;
} }
/**
* Make sure to unsubscribe from all existing subscription to prevent memory leaks
*/
ngOnDestroy(): void { ngOnDestroy(): void {
this.subs.forEach((sub) => { this.subs.forEach((sub) => {
sub.unsubscribe(); sub.unsubscribe();
}); });
} }
/**
* @returns {Observable<string>} Emits the current scope's identifier
*/
private getScopePart(defaultScope: string): Observable<any> { private getScopePart(defaultScope: string): Observable<any> {
return this.getCurrentScope(defaultScope).map((scope) => { return this.getCurrentScope(defaultScope).map((scope) => {
return { scope } return { scope }
@@ -183,7 +230,7 @@ export class SearchConfigurationService implements OnDestroy {
} }
/** /**
* @returns {Observable<string>} Emits the current query string * @returns {Observable<string>} Emits the current query string as a partial SearchOptions object
*/ */
private getQueryPart(defaultQuery: string): Observable<any> { private getQueryPart(defaultQuery: string): Observable<any> {
return this.getCurrentQuery(defaultQuery).map((query) => { return this.getCurrentQuery(defaultQuery).map((query) => {
@@ -192,7 +239,7 @@ export class SearchConfigurationService implements OnDestroy {
} }
/** /**
* @returns {Observable<string>} Emits the current pagination settings * @returns {Observable<string>} Emits the current pagination settings as a partial SearchOptions object
*/ */
private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> { private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable<any> {
return this.getCurrentPagination(defaultPagination).map((pagination) => { return this.getCurrentPagination(defaultPagination).map((pagination) => {
@@ -201,7 +248,7 @@ export class SearchConfigurationService implements OnDestroy {
} }
/** /**
* @returns {Observable<string>} Emits the current sorting settings * @returns {Observable<string>} Emits the current sorting settings as a partial SearchOptions object
*/ */
private getSortPart(defaultSort: SortOptions): Observable<any> { private getSortPart(defaultSort: SortOptions): Observable<any> {
return this.getCurrentSort(defaultSort).map((sort) => { return this.getCurrentSort(defaultSort).map((sort) => {
@@ -210,7 +257,7 @@ export class SearchConfigurationService implements OnDestroy {
} }
/** /**
* @returns {Observable<Params>} Emits the current active filters with their values as they are sent to the backend * @returns {Observable<Params>} Emits the current active filters as a partial SearchOptions object
*/ */
private getFiltersPart(): Observable<any> { private getFiltersPart(): Observable<any> {
return this.getCurrentFilters().map((filters) => { return this.getCurrentFilters().map((filters) => {

View File

@@ -8,7 +8,7 @@ export class PaginatedList<T> {
} }
get elementsPerPage(): number { get elementsPerPage(): number {
if (hasValue(this.pageInfo)) { if (hasValue(this.pageInfo) && hasValue(this.pageInfo.elementsPerPage)) {
return this.pageInfo.elementsPerPage; return this.pageInfo.elementsPerPage;
} }
return this.page.length; return this.page.length;
@@ -19,7 +19,7 @@ export class PaginatedList<T> {
} }
get totalElements(): number { get totalElements(): number {
if (hasValue(this.pageInfo)) { if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) {
return this.pageInfo.totalElements; return this.pageInfo.totalElements;
} }
return this.page.length; return this.page.length;
@@ -30,7 +30,7 @@ export class PaginatedList<T> {
} }
get totalPages(): number { get totalPages(): number {
if (hasValue(this.pageInfo)) { if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalPages)) {
return this.pageInfo.totalPages; return this.pageInfo.totalPages;
} }
return 1; return 1;
@@ -41,7 +41,7 @@ export class PaginatedList<T> {
} }
get currentPage(): number { get currentPage(): number {
if (hasValue(this.pageInfo)) { if (hasValue(this.pageInfo) && hasValue(this.pageInfo.currentPage)) {
return this.pageInfo.currentPage; return this.pageInfo.currentPage;
} }
return 1; return 1;

View File

@@ -1,15 +1,13 @@
import { import {
RegistryMetadatafieldsSuccessResponse, RegistryMetadataschemasSuccessResponse, RegistryMetadatafieldsSuccessResponse,
RestResponse RestResponse
} from '../cache/response-cache.models'; } from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { RestRequest } from './request.models'; import { RestRequest } from './request.models';
import { ResponseParsingService } from './parsing.service'; import { ResponseParsingService } from './parsing.service';
import { RegistryMetadataschemasResponse } from '../registry/registry-metadataschemas-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { DSOResponseParsingService } from './dso-response-parsing.service'; import { DSOResponseParsingService } from './dso-response-parsing.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { forEach } from '@angular/router/src/utils/collection';
import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model'; import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model';
@Injectable() @Injectable()

View File

@@ -5,7 +5,7 @@
(dsClickOutside)="close()"> (dsClickOutside)="close()">
<input #inputField type="text" [(ngModel)]="ngModel" [name]="name" <input #inputField type="text" [(ngModel)]="ngModel" [name]="name"
class="form-control suggestion_input" class="form-control suggestion_input"
[dsDebounce]="debounceTime" (onDebounce)="findSuggestions.emit($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <input type="submit" class="d-none"/>

View File

@@ -80,6 +80,11 @@ export class InputSuggestionsComponent {
*/ */
selectedIndex = -1; selectedIndex = -1;
/**
* True when the dropdown should not reopen
*/
blockReopen = false;
/** /**
* Reference to the input field component * Reference to the input field component
*/ */
@@ -162,13 +167,25 @@ export class InputSuggestionsComponent {
} }
/** /**
* Make sure that if a suggestion is clicked, the suggestions dropdown closes and the focus moves to the input field * Make sure that if a suggestion is clicked, the suggestions dropdown closes, does not reopen and the focus moves to the input field
*/ */
onClickSuggestion(data) { onClickSuggestion(data) {
this.clickSuggestion.emit(data); this.clickSuggestion.emit(data);
this.close(); this.close();
this.blockReopen = true;
this.queryInput.nativeElement.focus(); this.queryInput.nativeElement.focus();
return false; return false;
} }
/**
* Finds new suggestions when necessary
* @param data The query value to emit
*/
find(data) {
if (!this.blockReopen) {
this.findSuggestions.emit(data);
}
this.blockReopen = false;
}
} }

View File

@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { QueryParamsHandling } from '@angular/router/src/config'; import { QueryParamsHandling } from '@angular/router/src/config';
/** /**