diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index b409082b4a..c87f96ffba 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -87,7 +87,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { const facetValues = Observable.combineLatest(searchOptions, this.currentPage, (options, page) => { return { 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 { values: Observable.of(results), page: page diff --git a/src/app/+search-page/search-filters/search-filters.component.html b/src/app/+search-page/search-filters/search-filters.component.html index 1e5c474d65..0522c1fba0 100644 --- a/src/app/+search-page/search-filters/search-filters.component.html +++ b/src/app/+search-page/search-filters/search-filters.component.html @@ -1,7 +1,7 @@

{{"search.filters.head" | translate}}

- +
{{"search.filters.reset" | translate}} \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filters.component.spec.ts b/src/app/+search-page/search-filters/search-filters.component.spec.ts index 64e3a1d2c7..7f0d4ad748 100644 --- a/src/app/+search-page/search-filters/search-filters.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filters.component.spec.ts @@ -24,6 +24,12 @@ describe('SearchFiltersComponent', () => { } /* tslint:enable:no-empty */ }; + + const searchFiltersStub = { + getSelectedValuesForFilter: (filter) => + [] + }; + const searchConfigServiceStub = jasmine.createSpyObj('SearchConfigurationService', { getCurrentFrontendFilters: Observable.of({}) }); @@ -35,6 +41,7 @@ describe('SearchFiltersComponent', () => { providers: [ { provide: SearchService, useValue: searchServiceStub }, { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, + { provide: SearchFilterService, useValue: searchFiltersStub }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index 9c2a0c94be..684f4d94fe 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -4,6 +4,8 @@ import { RemoteData } from '../../core/data/remote-data'; import { SearchFilterConfig } from '../search-service/search-filter-config.model'; import { Observable } from 'rxjs/Observable'; import { SearchConfigurationService } from '../search-service/search-configuration.service'; +import { isNotEmpty } from '../../shared/empty.util'; +import { SearchFilterService } from './search-filter/search-filter.service'; @Component({ selector: 'ds-search-filters', @@ -30,10 +32,14 @@ export class SearchFiltersComponent { * Initialize instance variables * @param {SearchService} searchService * @param {SearchConfigurationService} searchConfigService + * @param {SearchFilterService} filterService */ - constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService) { - this.filters = searchService.getConfig(); - this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); + constructor(private searchService: SearchService, private searchConfigService: SearchConfigurationService, private filterService: SearchFilterService) { + this.filters = searchService.getConfig().first((RD) => !RD.isLoading); + this.clearParams = searchConfigService.getCurrentFrontendFilters().map((filters) => { + Object.keys(filters).forEach((f) => filters[f] = null); + return filters; + }); } /** @@ -42,4 +48,29 @@ export class SearchFiltersComponent { 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} Emits true whenever a given filter config should be shown + */ + isActive(filter: SearchFilterConfig): Observable { + // 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); + } } diff --git a/src/app/+search-page/search-labels/search-labels.component.html b/src/app/+search-page/search-labels/search-labels.component.html index 5113e764ff..61a5618dad 100644 --- a/src/app/+search-page/search-labels/search-labels.component.html +++ b/src/app/+search-page/search-labels/search-labels.component.html @@ -1,12 +1,13 @@ -
+ diff --git a/src/app/+search-page/search-labels/search-labels.component.scss b/src/app/+search-page/search-labels/search-labels.component.scss new file mode 100644 index 0000000000..c48cd57304 --- /dev/null +++ b/src/app/+search-page/search-labels/search-labels.component.scss @@ -0,0 +1,3 @@ +:host { + line-height: 1; +} \ No newline at end of file diff --git a/src/app/+search-page/search-labels/search-labels.component.ts b/src/app/+search-page/search-labels/search-labels.component.ts index f5215d7997..61482f8d8a 100644 --- a/src/app/+search-page/search-labels/search-labels.component.ts +++ b/src/app/+search-page/search-labels/search-labels.component.ts @@ -8,6 +8,7 @@ import { SearchConfigurationService } from '../search-service/search-configurati @Component({ selector: 'ds-search-labels', + styleUrls: ['./search-labels.component.scss'], templateUrl: './search-labels.component.html', }) diff --git a/src/app/+search-page/search-service/search-configuration.service.ts b/src/app/+search-page/search-service/search-configuration.service.ts index 95c4c60d65..8ad0b684ad 100644 --- a/src/app/+search-page/search-service/search-configuration.service.ts +++ b/src/app/+search-page/search-service/search-configuration.service.ts @@ -16,24 +16,55 @@ import { Subscription } from 'rxjs/Subscription'; */ @Injectable() export class SearchConfigurationService implements OnDestroy { + /** + * Default pagination settings + */ private defaultPagination = Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, currentPage: 1 }); + /** + * Default sort settings + */ private defaultSort = new SortOptions('score', SortDirection.DESC); + /** + * Default scope setting + */ private defaultScope = ''; + /** + * Default query setting + */ private defaultQuery = ''; - private _defaults; + /** + * Emits the current default values + */ + private _defaults: Observable>; + /** + * Emits the current search options + */ public searchOptions: BehaviorSubject; + + /** + * Emits the current search options including pagination and sort + */ public paginatedSearchOptions: BehaviorSubject; + + /** + * List of subscriptions to unsubscribe from on destroy + */ private subs: Subscription[] = new Array(); + /** + * Initialize the search options + * @param {RouteService} routeService + * @param {ActivatedRoute} route + */ constructor(private routeService: RouteService, private route: ActivatedRoute) { this.defaults.first().subscribe((defRD) => { @@ -128,6 +159,11 @@ export class SearchConfigurationService implements OnDestroy { 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 { return Observable.merge( 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 { return Observable.merge( this.getPaginationPart(defaults.pagination), @@ -170,12 +211,18 @@ export class SearchConfigurationService implements OnDestroy { return this._defaults; } + /** + * Make sure to unsubscribe from all existing subscription to prevent memory leaks + */ ngOnDestroy(): void { this.subs.forEach((sub) => { sub.unsubscribe(); }); } + /** + * @returns {Observable} Emits the current scope's identifier + */ private getScopePart(defaultScope: string): Observable { return this.getCurrentScope(defaultScope).map((scope) => { return { scope } @@ -183,7 +230,7 @@ export class SearchConfigurationService implements OnDestroy { } /** - * @returns {Observable} Emits the current query string + * @returns {Observable} Emits the current query string as a partial SearchOptions object */ private getQueryPart(defaultQuery: string): Observable { return this.getCurrentQuery(defaultQuery).map((query) => { @@ -192,7 +239,7 @@ export class SearchConfigurationService implements OnDestroy { } /** - * @returns {Observable} Emits the current pagination settings + * @returns {Observable} Emits the current pagination settings as a partial SearchOptions object */ private getPaginationPart(defaultPagination: PaginationComponentOptions): Observable { return this.getCurrentPagination(defaultPagination).map((pagination) => { @@ -201,7 +248,7 @@ export class SearchConfigurationService implements OnDestroy { } /** - * @returns {Observable} Emits the current sorting settings + * @returns {Observable} Emits the current sorting settings as a partial SearchOptions object */ private getSortPart(defaultSort: SortOptions): Observable { return this.getCurrentSort(defaultSort).map((sort) => { @@ -210,7 +257,7 @@ export class SearchConfigurationService implements OnDestroy { } /** - * @returns {Observable} Emits the current active filters with their values as they are sent to the backend + * @returns {Observable} Emits the current active filters as a partial SearchOptions object */ private getFiltersPart(): Observable { return this.getCurrentFilters().map((filters) => { diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts index 21cc13f3fa..07d53739d0 100644 --- a/src/app/core/data/paginated-list.ts +++ b/src/app/core/data/paginated-list.ts @@ -8,7 +8,7 @@ export class PaginatedList { } get elementsPerPage(): number { - if (hasValue(this.pageInfo)) { + if (hasValue(this.pageInfo) && hasValue(this.pageInfo.elementsPerPage)) { return this.pageInfo.elementsPerPage; } return this.page.length; @@ -19,7 +19,7 @@ export class PaginatedList { } get totalElements(): number { - if (hasValue(this.pageInfo)) { + if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) { return this.pageInfo.totalElements; } return this.page.length; @@ -30,7 +30,7 @@ export class PaginatedList { } get totalPages(): number { - if (hasValue(this.pageInfo)) { + if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalPages)) { return this.pageInfo.totalPages; } return 1; @@ -41,7 +41,7 @@ export class PaginatedList { } get currentPage(): number { - if (hasValue(this.pageInfo)) { + if (hasValue(this.pageInfo) && hasValue(this.pageInfo.currentPage)) { return this.pageInfo.currentPage; } return 1; diff --git a/src/app/core/data/registry-metadatafields-response-parsing.service.ts b/src/app/core/data/registry-metadatafields-response-parsing.service.ts index 2620916070..1fe8b1e15f 100644 --- a/src/app/core/data/registry-metadatafields-response-parsing.service.ts +++ b/src/app/core/data/registry-metadatafields-response-parsing.service.ts @@ -1,15 +1,13 @@ import { - RegistryMetadatafieldsSuccessResponse, RegistryMetadataschemasSuccessResponse, + RegistryMetadatafieldsSuccessResponse, RestResponse } from '../cache/response-cache.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; -import { RegistryMetadataschemasResponse } from '../registry/registry-metadataschemas-response.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { Injectable } from '@angular/core'; -import { forEach } from '@angular/router/src/utils/collection'; import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model'; @Injectable() diff --git a/src/app/shared/input-suggestions/input-suggestions.component.html b/src/app/shared/input-suggestions/input-suggestions.component.html index 02ef38dea0..bbe090dac0 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.html +++ b/src/app/shared/input-suggestions/input-suggestions.component.html @@ -5,7 +5,7 @@ (dsClickOutside)="close()"> diff --git a/src/app/shared/input-suggestions/input-suggestions.component.ts b/src/app/shared/input-suggestions/input-suggestions.component.ts index f95fd4611d..eb28583eaa 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.ts +++ b/src/app/shared/input-suggestions/input-suggestions.component.ts @@ -80,6 +80,11 @@ export class InputSuggestionsComponent { */ selectedIndex = -1; + /** + * True when the dropdown should not reopen + */ + blockReopen = false; + /** * 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) { this.clickSuggestion.emit(data); this.close(); + this.blockReopen = true; this.queryInput.nativeElement.focus(); 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; + } + } diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index a556e4c036..21a90daed4 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; 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'; /**