diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts index 00125e31f5..80ba200d38 100644 --- a/src/app/core/shared/search/search-filter.service.ts +++ b/src/app/core/shared/search/search-filter.service.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { Injectable, InjectionToken } from '@angular/core'; import { @@ -26,6 +26,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; export const FILTER_CONFIG: InjectionToken = new InjectionToken('filterConfig'); export const IN_PLACE_SEARCH: InjectionToken = new InjectionToken('inPlaceSearch'); +export const REFRESH_FILTER: InjectionToken> = new InjectionToken('refreshFilters'); /** * Service that performs all actions that have to do with search filters and facets diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 7911db026d..ffbe9fdfec 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -262,9 +262,11 @@ export class SearchService implements OnDestroy { * @param {number} valuePage The page number of the filter values * @param {SearchOptions} searchOptions The search configuration for the current search * @param {string} filterQuery The optional query used to filter out filter values + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true * @returns {Observable>>} Emits the given page of facet values */ - getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string): Observable> { + getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable> { let href; const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`]; if (hasValue(filterQuery)) { @@ -282,7 +284,7 @@ export class SearchService implements OnDestroy { return FacetValueResponseParsingService; } }); - this.requestService.send(request, true); + this.requestService.send(request, useCachedVersionIfAvailable); return this.rdb.buildFromHref(href); } diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index 999ae9a120..fc24defbc5 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -19,7 +19,7 @@ [importable]="importable" [importConfig]="importConfig" (importObject)="importObject.emit($event)" - (contentChange)="contentChange.emit()" + (contentChange)="contentChange.emit($event)" (prev)="goPrev()" (next)="goNext()" *ngIf="(currentMode$ | async) === viewModeEnum.ListElement"> @@ -49,6 +49,7 @@ [context]="context" [hidePaginationDetail]="hidePaginationDetail" [showPaginator]="showPaginator" + (contentChange)="contentChange.emit($event)" *ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index f2706f1a4c..42e8b3f19a 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -50,6 +50,11 @@ export class ObjectCollectionComponent implements OnInit { @Input() hideGear = false; @Input() selectable = false; @Input() selectionConfig: {repeatable: boolean, listId: string}; + + /** + * Emit custom event for listable object custom actions. + */ + @Output() customEvent = new EventEmitter(); @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index edf0b3ea7c..ff88256f6a 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -5,7 +5,9 @@ import { ListableObject } from '../listable-object.model'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { Context } from '../../../../core/shared/context.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; +import { + ItemListElementComponent +} from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; import { ListableObjectDirective } from './listable-object.directive'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; @@ -146,7 +148,7 @@ describe('ListableObjectComponentLoaderComponent', () => { expect((comp as any).instantiateComponent).not.toHaveBeenCalled(); (listableComponent as any).reloadedObject.emit(reloadedObject); - tick(); + tick(200); expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject); })); @@ -155,7 +157,7 @@ describe('ListableObjectComponentLoaderComponent', () => { expect((comp as any).contentChange.emit).not.toHaveBeenCalled(); (listableComponent as any).reloadedObject.emit(reloadedObject); - tick(); + tick(200); expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject); })); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 9c7ad5f659..366a78b891 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,16 +1,16 @@ import { Component, ComponentFactoryResolver, + ComponentRef, ElementRef, + EventEmitter, Input, + OnChanges, OnDestroy, OnInit, Output, - ViewChild, - EventEmitter, SimpleChanges, - OnChanges, - ComponentRef + ViewChild } from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -187,7 +187,10 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges this.compRef.destroy(); this.object = reloadedObject; this.instantiateComponent(reloadedObject); - this.contentChange.emit(reloadedObject); + // Add delay before emitting event to allow the new object is instantiated + setTimeout(() => { + this.contentChange.emit(reloadedObject); + }, 100); } }); } diff --git a/src/app/shared/object-detail/object-detail.component.html b/src/app/shared/object-detail/object-detail.component.html index 824e7f3dcc..05b8342ca3 100644 --- a/src/app/shared/object-detail/object-detail.component.html +++ b/src/app/shared/object-detail/object-detail.component.html @@ -14,12 +14,14 @@ (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)" (prev)="goPrev()" - (next)="goNext()" - > + (next)="goNext()">
- +
diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts index 05d2ca4b5b..15bd5b7bca 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - Output, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list.model'; @@ -71,6 +64,11 @@ export class ObjectDetailComponent { */ @Input() showPaginator = true; + /** + * Emit when one of the listed object has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * If showPaginator is set to true, emit when the previous button is clicked */ diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 927f2b9d2a..863d328a69 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -14,8 +14,7 @@ (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)" (prev)="goPrev()" - (next)="goNext()" - > + (next)="goNext()">
  • + (contentChange)="contentChange.emit($event)">
diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 166eb3f1ab..65e2b508da 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -74,7 +74,7 @@ export class ObjectListComponent { /** * Config used for the import button */ - @Input() importConfig: { importLabel: string }; + @Input() importConfig: { buttonLabel: string }; /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination @@ -221,4 +221,5 @@ export class ObjectListComponent { goNext() { this.next.emit(true); } + } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts index 3f83c766fe..0aa131d428 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts @@ -2,9 +2,14 @@ import { Component, Injector, Input, OnInit } from '@angular/core'; import { renderFilterType } from '../search-filter-type-decorator'; import { FilterType } from '../../../models/filter-type.model'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; -import { FILTER_CONFIG, IN_PLACE_SEARCH } from '../../../../../core/shared/search/search-filter.service'; +import { + FILTER_CONFIG, + IN_PLACE_SEARCH, + REFRESH_FILTER +} from '../../../../../core/shared/search/search-filter.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; +import { BehaviorSubject } from 'rxjs'; @Component({ selector: 'ds-search-facet-filter-wrapper', @@ -25,6 +30,11 @@ export class SearchFacetFilterWrapperComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: BehaviorSubject; + /** * The constructor of the search facet filter that should be rendered, based on the filter config's type */ @@ -45,7 +55,8 @@ export class SearchFacetFilterWrapperComponent implements OnInit { this.objectInjector = Injector.create({ providers: [ { provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] }, - { provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] } + { provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] }, + { provide: REFRESH_FILTER, useFactory: () => (this.refreshFilters), deps: [] } ], parent: this.injector }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts index ad8df3e3ac..92d2e5265b 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts @@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FilterType } from '../../../models/filter-type.model'; import { FacetValue } from '../../../models/facet-value.model'; import { FormsModule } from '@angular/forms'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; @@ -97,6 +98,7 @@ describe('SearchFacetFilterComponent', () => { { provide: RemoteDataBuildService, useValue: { aggregate: () => observableOf({}) } }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => observableOf(selectedValues), diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index ada1bccd63..c3c50f2922 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -6,7 +6,7 @@ import { Subject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @@ -21,6 +21,7 @@ import { SearchService } from '../../../../../core/shared/search/search.service' import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; @@ -98,7 +99,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { protected router: Router, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, - @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) { + @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject) { } /** @@ -110,66 +112,15 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); this.searchOptions$ = this.searchConfigService.searchOptions; - this.subs.push(this.searchOptions$.subscribe(() => this.updateFilterValueList())); - const facetValues$ = observableCombineLatest(this.searchOptions$, this.currentPage).pipe( - map(([options, page]) => { - return { options, page }; - }), - switchMap(({ options, page }) => { - return this.searchService.getFacetValuesFor(this.filterConfig, page, options) - .pipe( - getFirstSucceededRemoteData(), - map((results) => { - return { - values: observableOf(results), - page: page - }; - } - ) - ); + this.subs.push( + this.searchOptions$.subscribe(() => this.updateFilterValueList()), + this.refreshFilters.asObservable().pipe( + filter((toRefresh: boolean) => toRefresh), + ).subscribe(() => { + this.retrieveFilterValues(false); }) ); - - let filterValues = []; - this.subs.push(facetValues$.subscribe((facetOutcome) => { - const newValues$ = facetOutcome.values; - - if (this.collapseNextUpdate) { - this.showFirstPageOnly(); - facetOutcome.page = 1; - this.collapseNextUpdate = false; - } - if (facetOutcome.page === 1) { - filterValues = []; - } - - filterValues = [...filterValues, newValues$]; - - this.subs.push(this.rdbs.aggregate(filterValues).pipe( - tap((rd: RemoteData[]>) => { - this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( - map((selectedValues) => { - return selectedValues.map((value: string) => { - const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); - if (hasValue(fValue)) { - return fValue; - } - const filterValue = stripOperatorFromFilterValue(value); - return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); - }); - }) - ); - }) - ).subscribe((rd: RemoteData[]>) => { - this.animationState = 'ready'; - this.filterValues$.next(rd); - - })); - this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => { - this.isLastPage$.next(hasNoValue(rd.payload.next)); - })); - })); - + this.retrieveFilterValues(); } /** @@ -324,6 +275,67 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return getFacetValueForType(facet, this.filterConfig); } + protected retrieveFilterValues(useCachedVersionIfAvailable = true) { + const facetValues$ = observableCombineLatest([this.searchOptions$, this.currentPage]).pipe( + map(([options, page]) => { + return { options, page }; + }), + switchMap(({ options, page }) => { + return this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable) + .pipe( + getFirstSucceededRemoteData(), + map((results) => { + return { + values: observableOf(results), + page: page + }; + } + ) + ); + }) + ); + + let filterValues = []; + this.subs.push(facetValues$.subscribe((facetOutcome) => { + const newValues$ = facetOutcome.values; + + if (this.collapseNextUpdate) { + this.showFirstPageOnly(); + facetOutcome.page = 1; + this.collapseNextUpdate = false; + } + if (facetOutcome.page === 1) { + filterValues = []; + } + + filterValues = [...filterValues, newValues$]; + + this.subs.push(this.rdbs.aggregate(filterValues).pipe( + tap((rd: RemoteData[]>) => { + this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( + map((selectedValues) => { + return selectedValues.map((value: string) => { + const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); + if (hasValue(fValue)) { + return fValue; + } + const filterValue = stripOperatorFromFilterValue(value); + return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); + }); + }) + ); + }) + ).subscribe((rd: RemoteData[]>) => { + this.animationState = 'ready'; + this.filterValues$.next(rd); + + })); + this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => { + this.isLastPage$.next(hasNoValue(rd.payload.next)); + })); + })); + } + /** * Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value * @param {FacetValue} facet The value of the facet as returned by the server diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-filter.component.html index 452433e165..13457cc008 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.html @@ -19,7 +19,8 @@ class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }"> + [inPlaceSearch]="inPlaceSearch" + [refreshFilters]="refreshFilters" > diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 1897169a2e..d1d3bd729d 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, Input, OnInit } from '@angular/core'; -import { Observable, of as observableOf } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; import { SearchFilterConfig } from '../../models/search-filter-config.model'; @@ -33,6 +33,11 @@ export class SearchFilterComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: BehaviorSubject; + /** * True when the filter is 100% collapsed in the UI */ diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts index c59ae7a906..9302e66d98 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts @@ -1,17 +1,18 @@ -import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SearchHierarchyFilterComponent } from './search-hierarchy-filter.component'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { - SearchFilterService, FILTER_CONFIG, - IN_PLACE_SEARCH + IN_PLACE_SEARCH, + REFRESH_FILTER, + SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { SearchFiltersComponent } from '../../search-filters.component'; import { Router } from '@angular/router'; import { RouterStub } from '../../../../testing/router.stub'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; -import { of as observableOf, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../../../testing/search-configuration-service.stub'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; @@ -21,7 +22,7 @@ import { } from '../../../../input-suggestions/filter-suggestions/filter-input-suggestions.component'; import { FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; import { FacetValue } from '../../../models/facet-value.model'; import { FilterType } from '../../../models/filter-type.model'; @@ -112,7 +113,8 @@ describe('SearchHierarchyFilterComponent', () => { { provide: Router, useValue: new RouterStub() }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, - { provide: FILTER_CONFIG, useValue: new SearchFilterConfig() } + { provide: FILTER_CONFIG, useValue: new SearchFilterConfig() }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(SearchHierarchyFilterComponent, { @@ -140,7 +142,7 @@ describe('SearchHierarchyFilterComponent', () => { }); it('should navigate to the correct filter with the query operator', () => { - expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}); + expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}, null, true); const searchQuery = 'MARVEL'; comp.onSubmit(searchQuery); diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index bc2f60b357..44dda40d15 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FilterType } from '../../../models/filter-type.model'; import { FacetValue } from '../../../models/facet-value.model'; import { FormsModule } from '@angular/forms'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; @@ -104,6 +105,7 @@ describe('SearchRangeFilterComponent', () => { { provide: RouteService, useValue: { getQueryParameterValue: () => observableOf({}) } }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => selectedValues, diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index a6b33ddf88..fbd767284f 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { isPlatformBrowser } from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; @@ -10,6 +10,7 @@ import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchService } from '../../../../../core/shared/search/search.service'; @@ -86,8 +87,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @Inject(PLATFORM_ID) private platformId: any, + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject, private route: RouteService) { - super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig); + super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters); } diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index 488ae7b517..e392cd2663 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,7 +1,7 @@

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

- +
{{"search.filters.reset" | translate}} diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index a7d1f7bd8b..766939226d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -53,7 +53,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { /** * Emits when the search filters values may be stale, and so they must be refreshed. */ - @Input() refreshFilters: Observable; + @Input() refreshFilters: BehaviorSubject; /** * Link to the search page diff --git a/src/app/shared/search/search-results/search-results.component.html b/src/app/shared/search/search-results/search-results.component.html index d557af2451..44498c3cab 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,6 +1,6 @@
-

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

- +

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

+
+ (selectObject)="selectObject.emit($event)">
- +
{{ 'search.results.no-results' | translate }} (); + @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/search/search-results/themed-search-results.component.ts b/src/app/shared/search/search-results/themed-search-results.component.ts index 19a8fc55e8..deb64bf840 100644 --- a/src/app/shared/search/search-results/themed-search-results.component.ts +++ b/src/app/shared/search/search-results/themed-search-results.component.ts @@ -21,7 +21,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m templateUrl: '../../theme-support/themed.component.html', }) export class ThemedSearchResultsComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'deselectObject', 'selectObject']; + protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'contentChange', 'deselectObject', 'selectObject']; @Input() linkType: CollectionElementLinkType; @@ -45,6 +45,8 @@ export class ThemedSearchResultsComponent extends ThemedComponent = new EventEmitter(); + @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 0212dc2497..929f819ca3 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -83,7 +83,7 @@ export class SearchSidebarComponent { /** * Emits when the search filters values may be stale, and so they must be refreshed. */ - @Input() refreshFilters: Observable; + @Input() refreshFilters: BehaviorSubject; /** * Emits event when the user clicks a button to open or close the sidebar diff --git a/src/app/shared/search/search.component.html b/src/app/shared/search/search.component.html index d7dab8c126..cdad62dcbd 100644 --- a/src/app/shared/search/search.component.html +++ b/src/app/shared/search/search.component.html @@ -37,6 +37,7 @@ [context]="(currentContext$ | async)" [selectable]="selectable" [selectionConfig]="selectionConfig" + (contentChange)="onContentChange($event)" (deselectObject)="deselectObject.emit($event)" (selectObject)="selectObject.emit($event)">
@@ -49,6 +50,7 @@ [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" [filters]="filtersRD$.asObservable()" + [refreshFilters]="refreshFilters" [resultCount]="(resultsRD$ | async)?.payload?.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" @@ -63,6 +65,7 @@ [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" [filters]="filtersRD$.asObservable()" + [refreshFilters]="refreshFilters" [resultCount]="(resultsRD$ | async)?.payload.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 03e76c2f0b..d035c5fd73 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -201,6 +201,11 @@ export class SearchComponent implements OnInit { */ isXsOrSm$: Observable; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + refreshFilters: BehaviorSubject = new BehaviorSubject(false); + /** * Link to the search page */ @@ -339,6 +344,15 @@ export class SearchComponent implements OnInit { this.sidebarService.expand(); } + /** + * Emit event to refresh filter content + * @param $event + */ + public onContentChange($event: any) { + this.retrieveFilters(this.lastSearchOptions); + this.refreshFilters.next(true); + } + /** * Unsubscribe from the subscription */