[CST-6494] Update filter values when a workflow action is dispatched

This commit is contained in:
Giuseppe Digilio
2022-09-21 09:54:10 +02:00
parent 6d3f3cad2f
commit e072cdf75b
26 changed files with 187 additions and 114 deletions

View File

@@ -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<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
/**
* Service that performs all actions that have to do with search filters and facets

View File

@@ -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<RemoteData<PaginatedList<FacetValue>>>} Emits the given page of facet values
*/
getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string): Observable<RemoteData<FacetValues>> {
getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable<RemoteData<FacetValues>> {
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);
}

View File

@@ -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">
</ds-object-detail>

View File

@@ -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<any>();
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();

View File

@@ -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);
}));

View File

@@ -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);
// Add delay before emitting event to allow the new object is instantiated
setTimeout(() => {
this.contentChange.emit(reloadedObject);
}, 100);
}
});
}

View File

@@ -14,12 +14,14 @@
(sortFieldChange)="onSortFieldChange($event)"
(paginationChange)="onPaginationChange($event)"
(prev)="goPrev()"
(next)="goNext()"
>
(next)="goNext()">
<div class="row mt-2" *ngIf="objects?.hasSucceeded" @fadeIn>
<div class="col"
*ngFor="let object of objects?.payload?.page">
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [context]="context"></ds-listable-object-component-loader>
<ds-listable-object-component-loader [object]="object"
[viewMode]="viewMode"
[context]="context"
(contentChange)="contentChange.emit($event)"></ds-listable-object-component-loader>
</div>
</div>
<ds-error *ngIf="objects.hasFailed" message="{{'error.objects' | translate}}"></ds-error>

View File

@@ -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<any>();
/**
* If showPaginator is set to true, emit when the previous button is clicked
*/

View File

@@ -14,8 +14,7 @@
(sortFieldChange)="onSortFieldChange($event)"
(paginationChange)="onPaginationChange($event)"
(prev)="goPrev()"
(next)="goNext()"
>
(next)="goNext()">
<ul *ngIf="objects?.hasSucceeded" class="list-unstyled" [ngClass]="{'ml-4': selectable}">
<li *ngFor="let object of objects?.payload?.page; let i = index; let last = last" class="mt-4 mb-4 d-flex" [class.border-bottom]="hasBorder && !last" [attr.data-test]="'list-object' | dsBrowserOnly">
<ds-selectable-list-item-control *ngIf="selectable" [index]="i"
@@ -28,8 +27,7 @@
(importObject)="importObject.emit($event)"></ds-importable-list-item-control>
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode" [index]="i" [context]="context" [linkType]="linkType"
[listID]="selectionConfig?.listId"
(contentChange)="contentChange.emit()"
></ds-listable-object-component-loader>
(contentChange)="contentChange.emit($event)"></ds-listable-object-component-loader>
</li>
</ul>
</ds-pagination>

View File

@@ -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);
}
}

View File

@@ -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<boolean>;
/**
* 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
});

View File

@@ -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<boolean>(false) },
{
provide: SearchFilterService, useValue: {
getSelectedValuesForFilter: () => observableOf(selectedValues),

View File

@@ -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<boolean>) {
}
/**
@@ -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<PaginatedList<FacetValue>[]>) => {
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<PaginatedList<FacetValue>[]>) => {
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<PaginatedList<FacetValue>[]>) => {
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<PaginatedList<FacetValue>[]>) => {
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

View File

@@ -19,7 +19,8 @@
class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }">
<ds-search-facet-filter-wrapper
[filterConfig]="filter"
[inPlaceSearch]="inPlaceSearch">
[inPlaceSearch]="inPlaceSearch"
[refreshFilters]="refreshFilters" >
</ds-search-facet-filter-wrapper>
</div>
</div>

View File

@@ -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<boolean>;
/**
* True when the filter is 100% collapsed in the UI
*/

View File

@@ -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<boolean>(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);

View File

@@ -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<boolean>(false) },
{
provide: SearchFilterService, useValue: {
getSelectedValuesForFilter: () => selectedValues,

View File

@@ -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<boolean>,
private route: RouteService) {
super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig);
super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters);
}

View File

@@ -1,7 +1,7 @@
<h3>{{"search.filters.head" | translate}}</h3>
<div *ngIf="(filters | async)?.hasSucceeded">
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
<ds-search-filter [filter]="filter" [inPlaceSearch]="inPlaceSearch"></ds-search-filter>
<ds-search-filter [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
</div>
</div>
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button"><i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}</a>

View File

@@ -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<any>;
@Input() refreshFilters: BehaviorSubject<boolean>;
/**
* Link to the search page

View File

@@ -1,6 +1,6 @@
<div class="d-flex justify-content-between">
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
<ds-search-export-csv [searchConfig]="searchConfig"></ds-search-export-csv>
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
<ds-search-export-csv [searchConfig]="searchConfig"></ds-search-export-csv>
</div>
<div *ngIf="searchResults && searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
<ds-viewable-collection
@@ -13,14 +13,13 @@
[linkType]="linkType"
[context]="context"
[hidePaginationDetail]="hidePaginationDetail"
(contentChange)="contentChange.emit($event)"
(deselectObject)="deselectObject.emit($event)"
(selectObject)="selectObject.emit($event)"
>
(selectObject)="selectObject.emit($event)">
</ds-viewable-collection>
</div>
<ds-themed-loading *ngIf="isLoading()" message="{{'loading.search-results' | translate}}"></ds-themed-loading>
<ds-error
*ngIf="showError()"
<ds-error *ngIf="showError()"
message="{{errorMessageLabel() | translate}}"></ds-error>
<div *ngIf="searchResults?.payload?.page.length == 0 || searchResults?.statusCode == 400">
{{ 'search.results.no-results' | translate }}

View File

@@ -85,6 +85,11 @@ export class SearchResultsComponent {
*/
@Input() selectionConfig: SelectionConfig = null;
/**
* Emit when one of the listed object has changed.
*/
@Output() contentChange = new EventEmitter<any>();
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();

View File

@@ -21,7 +21,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
templateUrl: '../../theme-support/themed.component.html',
})
export class ThemedSearchResultsComponent extends ThemedComponent<SearchResultsComponent> {
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<SearchResultsC
@Input() selectionConfig: SelectionConfig = null;
@Output() contentChange: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();

View File

@@ -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<any>;
@Input() refreshFilters: BehaviorSubject<boolean>;
/**
* Emits event when the user clicks a button to open or close the sidebar

View File

@@ -37,6 +37,7 @@
[context]="(currentContext$ | async)"
[selectable]="selectable"
[selectionConfig]="selectionConfig"
(contentChange)="onContentChange($event)"
(deselectObject)="deselectObject.emit($event)"
(selectObject)="selectObject.emit($event)"></ds-themed-search-results>
</div>
@@ -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)"

View File

@@ -201,6 +201,11 @@ export class SearchComponent implements OnInit {
*/
isXsOrSm$: Observable<boolean>;
/**
* Emits when the search filters values may be stale, and so they must be refreshed.
*/
refreshFilters: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(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
*/