mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-15 22:13:02 +00:00
optimizations links in facets
This commit is contained in:
@@ -119,6 +119,7 @@
|
|||||||
"pem": "1.12.3",
|
"pem": "1.12.3",
|
||||||
"reflect-metadata": "0.1.12",
|
"reflect-metadata": "0.1.12",
|
||||||
"rxjs": "6.2.2",
|
"rxjs": "6.2.2",
|
||||||
|
"rxjs-spy": "^7.5.1",
|
||||||
"sortablejs": "1.7.0",
|
"sortablejs": "1.7.0",
|
||||||
"text-mask-core": "5.0.1",
|
"text-mask-core": "5.0.1",
|
||||||
"ts-loader": "^5.2.1",
|
"ts-loader": "^5.2.1",
|
||||||
|
@@ -1,24 +1,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="filters py-2">
|
<div class="filters py-2">
|
||||||
<a *ngFor="let value of (selectedValues | async)" class="d-flex flex-row"
|
<ds-search-facet-selected-option *ngFor="let value of (selectedValues | async)" [selectedValue]="value" [filterConfig]="filterConfig"></ds-search-facet-selected-option>
|
||||||
[routerLink]="[getSearchLink()]"
|
|
||||||
[queryParams]="getRemoveParams(value) | async" queryParamsHandling="merge">
|
|
||||||
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
|
|
||||||
<span class="filter-value pl-1">{{value}}</span>
|
|
||||||
</a>
|
|
||||||
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
||||||
<div [@facetLoad]="animationState">
|
<div [@facetLoad]="animationState">
|
||||||
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value">
|
||||||
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
|
</ds-search-facet-option>
|
||||||
[routerLink]="[getSearchLink()]"
|
|
||||||
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge">
|
|
||||||
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
|
|
||||||
<span class="filter-value px-1">{{value.value}}</span>
|
|
||||||
<span class="float-right filter-value-count ml-auto">
|
|
||||||
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="clearfix toggle-more-filters">
|
<div class="clearfix toggle-more-filters">
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
<a *ngIf="isVisible" class="d-flex flex-row"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="addQueryParams | async" queryParamsHandling="merge">
|
||||||
|
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
|
||||||
|
<span class="filter-value px-1">{{filterValue.value}}</span>
|
||||||
|
<span class="float-right filter-value-count ml-auto">
|
||||||
|
<span class="badge badge-secondary badge-pill">{{filterValue.count}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
@@ -0,0 +1,78 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FacetValue } from '../../../../search-service/facet-value.model';
|
||||||
|
import { SearchFilterConfig } from '../../../../search-service/search-filter-config.model';
|
||||||
|
import { SearchService } from '../../../../search-service/search.service';
|
||||||
|
import { SearchFilterService } from '../../search-filter.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-facet-option',
|
||||||
|
templateUrl: './search-facet-option.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single option in a filter facet
|
||||||
|
*/
|
||||||
|
export class SearchFacetOptionComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* A single value for this component
|
||||||
|
*/
|
||||||
|
@Input() filterValue: FacetValue;
|
||||||
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the active values for this filter
|
||||||
|
*/
|
||||||
|
selectedValues$: Observable<string[]>;
|
||||||
|
|
||||||
|
isVisible: Observable<boolean>;
|
||||||
|
|
||||||
|
addQueryParams;
|
||||||
|
|
||||||
|
constructor(protected searchService: SearchService,
|
||||||
|
protected filterService: SearchFilterService,
|
||||||
|
protected router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all observable instance variables and starts listening to them
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
|
this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked));
|
||||||
|
this.addQueryParams = this.getAddParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a value for this filter is currently active
|
||||||
|
*/
|
||||||
|
private isChecked(): Observable<boolean> {
|
||||||
|
return this.filterService.isFilterActiveWithValue(this.filterConfig.paramName, this.filterValue.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} The base path to the search page
|
||||||
|
*/
|
||||||
|
getSearchLink() {
|
||||||
|
return this.searchService.getSearchLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the parameters that should change if a given value for this filter would be added to the active filters
|
||||||
|
* @param {string} value The value that is added for this filter
|
||||||
|
* @returns {Observable<any>} The changed filter parameters
|
||||||
|
*/
|
||||||
|
private getAddParams(): Observable<any> {
|
||||||
|
return this.selectedValues$.pipe(map((selectedValues) => {
|
||||||
|
return {
|
||||||
|
[this.filterConfig.paramName]: [...selectedValues, this.filterValue.value],
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,8 @@
|
|||||||
|
<a *ngIf="isVisible" class="d-flex flex-row"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="changeQueryParams | async" queryParamsHandling="merge">
|
||||||
|
<span class="filter-value px-1">{{filterValue.value}}</span>
|
||||||
|
<span class="float-right filter-value-count ml-auto">
|
||||||
|
<span class="badge badge-secondary badge-pill">{{filterValue.count}}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
@@ -0,0 +1,88 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FacetValue } from '../../../../search-service/facet-value.model';
|
||||||
|
import { SearchFilterConfig } from '../../../../search-service/search-filter-config.model';
|
||||||
|
import { SearchService } from '../../../../search-service/search.service';
|
||||||
|
import { SearchFilterService } from '../../search-filter.service';
|
||||||
|
import {
|
||||||
|
RANGE_FILTER_MAX_SUFFIX,
|
||||||
|
RANGE_FILTER_MIN_SUFFIX
|
||||||
|
} from '../../search-range-filter/search-range-filter.component';
|
||||||
|
|
||||||
|
const rangeDelimiter = '-';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-facet-range-option',
|
||||||
|
templateUrl: './search-facet-range-option.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single option in a filter facet
|
||||||
|
*/
|
||||||
|
export class SearchFacetRangeOptionComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* A single value for this component
|
||||||
|
*/
|
||||||
|
@Input() filterValue: FacetValue;
|
||||||
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the active values for this filter
|
||||||
|
*/
|
||||||
|
selectedValues$: Observable<string[]>;
|
||||||
|
|
||||||
|
isVisible: Observable<boolean>;
|
||||||
|
|
||||||
|
changeQueryParams;
|
||||||
|
|
||||||
|
constructor(protected searchService: SearchService,
|
||||||
|
protected filterService: SearchFilterService,
|
||||||
|
protected router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all observable instance variables and starts listening to them
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
|
this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked));
|
||||||
|
this.changeQueryParams = this.getChangeParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a value for this filter is currently active
|
||||||
|
*/
|
||||||
|
private isChecked(): Observable<boolean> {
|
||||||
|
return this.filterService.isFilterActiveWithValue(this.filterConfig.paramName, this.filterValue.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} The base path to the search page
|
||||||
|
*/
|
||||||
|
getSearchLink() {
|
||||||
|
return this.searchService.getSearchLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the parameters that should change if a given values for this range filter would be changed
|
||||||
|
* @param {string} value The values that are changed for this filter
|
||||||
|
* @returns {Observable<any>} The changed filter parameters
|
||||||
|
*/
|
||||||
|
getChangeParams() {
|
||||||
|
const parts = this.filterValue.value.split(rangeDelimiter);
|
||||||
|
const min = parts.length > 1 ? parts[0].trim() : this.filterValue.value;
|
||||||
|
const max = parts.length > 1 ? parts[1].trim() : this.filterValue.value;
|
||||||
|
return observableOf(
|
||||||
|
{
|
||||||
|
[this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: [min],
|
||||||
|
[this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: [max],
|
||||||
|
page: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,6 @@
|
|||||||
|
<a class="d-flex flex-row"
|
||||||
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="removeQueryParams | async" queryParamsHandling="merge">
|
||||||
|
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
|
||||||
|
<span class="filter-value pl-1">{{selectedValue}}</span>
|
||||||
|
</a>
|
@@ -0,0 +1,67 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { FacetValue } from '../../../../search-service/facet-value.model';
|
||||||
|
import { SearchFilterConfig } from '../../../../search-service/search-filter-config.model';
|
||||||
|
import { SearchService } from '../../../../search-service/search.service';
|
||||||
|
import { SearchFilterService } from '../../search-filter.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-facet-selected-option',
|
||||||
|
templateUrl: './search-facet-selected-option.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single option in a filter facet
|
||||||
|
*/
|
||||||
|
export class SearchFacetSelectedOptionComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* A single value for this component
|
||||||
|
*/
|
||||||
|
@Input() selectedValue: string;
|
||||||
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the active values for this filter
|
||||||
|
*/
|
||||||
|
selectedValues$: Observable<string[]>;
|
||||||
|
|
||||||
|
removeQueryParams;
|
||||||
|
|
||||||
|
constructor(protected searchService: SearchService,
|
||||||
|
protected filterService: SearchFilterService,
|
||||||
|
protected router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all observable instance variables and starts listening to them
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
|
this.removeQueryParams = this.getRemoveParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} The base path to the search page
|
||||||
|
*/
|
||||||
|
getSearchLink() {
|
||||||
|
return this.searchService.getSearchLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the parameters that should change if a given value for this filter would be removed from the active filters
|
||||||
|
* @param {string} value The value that is removed for this filter
|
||||||
|
* @returns {Observable<any>} The changed filter parameters
|
||||||
|
*/
|
||||||
|
private getRemoveParams(): Observable<any> {
|
||||||
|
return this.selectedValues$.pipe(map((selectedValues) => {
|
||||||
|
return {
|
||||||
|
[this.filterConfig.paramName]: selectedValues.filter((v) => v !== this.selectedValue),
|
||||||
|
page: 1
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1 +1 @@
|
|||||||
<ng-container *ngComponentOutlet="getSearchFilter(); injector: objectInjector;"></ng-container>
|
<ng-container *ngComponentOutlet="searchFilter injector: objectInjector;"></ng-container>
|
@@ -3,6 +3,8 @@ import { renderFilterType } from '../search-filter-type-decorator';
|
|||||||
import { FilterType } from '../../../search-service/filter-type.model';
|
import { FilterType } from '../../../search-service/filter-type.model';
|
||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { FILTER_CONFIG } from '../search-filter.service';
|
import { FILTER_CONFIG } from '../search-filter.service';
|
||||||
|
import { GenericConstructor } from '../../../../core/shared/generic-constructor';
|
||||||
|
import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter-wrapper',
|
selector: 'ds-search-facet-filter-wrapper',
|
||||||
@@ -18,6 +20,7 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() filterConfig: SearchFilterConfig;
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
|
|
||||||
|
searchFilter: GenericConstructor<SearchFacetFilterComponent>;
|
||||||
/**
|
/**
|
||||||
* Injector to inject a child component with the @Input parameters
|
* Injector to inject a child component with the @Input parameters
|
||||||
*/
|
*/
|
||||||
@@ -30,6 +33,7 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
|||||||
* Initialize and add the filter config to the injector
|
* Initialize and add the filter config to the injector
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.searchFilter = this.getSearchFilter();
|
||||||
this.objectInjector = Injector.create({
|
this.objectInjector = Injector.create({
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] }
|
{ provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] }
|
||||||
@@ -41,7 +45,7 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Find the correct component based on the filter config's type
|
* Find the correct component based on the filter config's type
|
||||||
*/
|
*/
|
||||||
getSearchFilter() {
|
private getSearchFilter() {
|
||||||
const type: FilterType = this.filterConfig.type;
|
const type: FilterType = this.filterConfig.type;
|
||||||
return renderFilterType(type);
|
return renderFilterType(type);
|
||||||
}
|
}
|
||||||
|
@@ -85,8 +85,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
* Initializes all observable instance variables and starts listening to them
|
* Initializes all observable instance variables and starts listening to them
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
console.log('renderSearchFacetFilterComponent')
|
||||||
|
|
||||||
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
|
||||||
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
|
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
|
||||||
|
|
||||||
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
this.selectedValues = this.filterService.getSelectedValuesForFilter(this.filterConfig);
|
||||||
const searchOptions = this.searchConfigService.searchOptions;
|
const searchOptions = this.searchConfigService.searchOptions;
|
||||||
this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList()));
|
this.subs.push(this.searchConfigService.searchOptions.subscribe(() => this.updateFilterValueList()));
|
||||||
@@ -190,6 +193,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
* @param data The string from the input field
|
* @param data The string from the input field
|
||||||
*/
|
*/
|
||||||
onSubmit(data: any) {
|
onSubmit(data: any) {
|
||||||
|
console.log('onsubmit');
|
||||||
this.selectedValues.pipe(take(1)).subscribe((selectedValues) => {
|
this.selectedValues.pipe(take(1)).subscribe((selectedValues) => {
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
this.router.navigate([this.getSearchLink()], {
|
this.router.navigate([this.getSearchLink()], {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
import { type } from '../../../shared/ngrx/type';
|
import { type } from '../../../shared/ngrx/type';
|
||||||
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For each action type in an action group, make a simple
|
* For each action type in an action group, make a simple
|
||||||
@@ -12,9 +13,8 @@ import { type } from '../../../shared/ngrx/type';
|
|||||||
*/
|
*/
|
||||||
export const SearchFilterActionTypes = {
|
export const SearchFilterActionTypes = {
|
||||||
COLLAPSE: type('dspace/search-filter/COLLAPSE'),
|
COLLAPSE: type('dspace/search-filter/COLLAPSE'),
|
||||||
INITIAL_COLLAPSE: type('dspace/search-filter/INITIAL_COLLAPSE'),
|
INITIALIZE: type('dspace/search-filter/INITIALIZE'),
|
||||||
EXPAND: type('dspace/search-filter/EXPAND'),
|
EXPAND: type('dspace/search-filter/EXPAND'),
|
||||||
INITIAL_EXPAND: type('dspace/search-filter/INITIAL_EXPAND'),
|
|
||||||
TOGGLE: type('dspace/search-filter/TOGGLE'),
|
TOGGLE: type('dspace/search-filter/TOGGLE'),
|
||||||
DECREMENT_PAGE: type('dspace/search-filter/DECREMENT_PAGE'),
|
DECREMENT_PAGE: type('dspace/search-filter/DECREMENT_PAGE'),
|
||||||
INCREMENT_PAGE: type('dspace/search-filter/INCREMENT_PAGE'),
|
INCREMENT_PAGE: type('dspace/search-filter/INCREMENT_PAGE'),
|
||||||
@@ -66,15 +66,13 @@ export class SearchFilterToggleAction extends SearchFilterAction {
|
|||||||
/**
|
/**
|
||||||
* Used to set the initial state of a filter to collapsed
|
* Used to set the initial state of a filter to collapsed
|
||||||
*/
|
*/
|
||||||
export class SearchFilterInitialCollapseAction extends SearchFilterAction {
|
export class SearchFilterInitializeAction extends SearchFilterAction {
|
||||||
type = SearchFilterActionTypes.INITIAL_COLLAPSE;
|
type = SearchFilterActionTypes.INITIALIZE;
|
||||||
}
|
initiallyExpanded;
|
||||||
|
constructor(filter: SearchFilterConfig) {
|
||||||
/**
|
super(filter.name);
|
||||||
* Used to set the initial state of a filter to expanded
|
this.initiallyExpanded = filter.isOpenByDefault;
|
||||||
*/
|
}
|
||||||
export class SearchFilterInitialExpandAction extends SearchFilterAction {
|
|
||||||
type = SearchFilterActionTypes.INITIAL_EXPAND;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div *ngIf="active$ | async">
|
||||||
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fas float-right"
|
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fas float-right"
|
||||||
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
||||||
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{'closed' : collapsed}">
|
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{'closed' : closed}">
|
||||||
<ds-search-facet-filter-wrapper [filterConfig]="filter"></ds-search-facet-filter-wrapper>
|
<ds-search-facet-filter-wrapper [filterConfig]="filter"></ds-search-facet-filter-wrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -1,11 +1,12 @@
|
|||||||
|
import { filter, first, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { SearchFilterService } from './search-filter.service';
|
import { SearchFilterService } from './search-filter.service';
|
||||||
import { Observable } from 'rxjs';
|
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
||||||
import { slide } from '../../../shared/animations/slide';
|
import { slide } from '../../../shared/animations/slide';
|
||||||
import { isNotEmpty } from '../../../shared/empty.util';
|
import { isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
import { SearchService } from '../../search-service/search.service';
|
||||||
|
import { SearchConfigurationService } from '../../search-service/search-configuration.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-filter',
|
selector: 'ds-search-filter',
|
||||||
@@ -26,9 +27,13 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* True when the filter is 100% collapsed in the UI
|
* True when the filter is 100% collapsed in the UI
|
||||||
*/
|
*/
|
||||||
collapsed;
|
closed = true;
|
||||||
|
collapsed$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(private filterService: SearchFilterService) {
|
selectedValues$: Observable<string[]>;
|
||||||
|
active$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(private filterService: SearchFilterService, private searchService: SearchService, private searchConfigService: SearchConfigurationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,11 +42,13 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
* Else, the filter should initially be collapsed
|
* Else, the filter should initially be collapsed
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.getSelectedValues().pipe(take(1)).subscribe((isActive) => {
|
this.selectedValues$ = this.getSelectedValues();
|
||||||
if (this.filter.isOpenByDefault || isNotEmpty(isActive)) {
|
this.active$ = this.isActive();
|
||||||
this.initialExpand();
|
this.collapsed$ = this.isCollapsed();
|
||||||
} else {
|
this.initializeFilter();
|
||||||
this.initialCollapse();
|
this.selectedValues$.pipe(take(1)).subscribe((selectedValues) => {
|
||||||
|
if (isNotEmpty(selectedValues)) {
|
||||||
|
this.filterService.expand(this.filter.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -57,30 +64,21 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
* Checks if the filter is currently collapsed
|
* Checks if the filter is currently collapsed
|
||||||
* @returns {Observable<boolean>} Emits true when the current state of the filter is collapsed, false when it's expanded
|
* @returns {Observable<boolean>} Emits true when the current state of the filter is collapsed, false when it's expanded
|
||||||
*/
|
*/
|
||||||
isCollapsed(): Observable<boolean> {
|
private isCollapsed(): Observable<boolean> {
|
||||||
return this.filterService.isCollapsed(this.filter.name);
|
return this.filterService.isCollapsed(this.filter.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the initial state to collapsed
|
* Sets the initial state of the filter
|
||||||
*/
|
*/
|
||||||
initialCollapse() {
|
initializeFilter() {
|
||||||
this.filterService.initialCollapse(this.filter.name);
|
this.filterService.initializeFilter(this.filter);
|
||||||
this.collapsed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the initial state to expanded
|
|
||||||
*/
|
|
||||||
initialExpand() {
|
|
||||||
this.filterService.initialExpand(this.filter.name);
|
|
||||||
this.collapsed = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<string[]>} Emits a list of all values that are currently active for this filter
|
* @returns {Observable<string[]>} Emits a list of all values that are currently active for this filter
|
||||||
*/
|
*/
|
||||||
getSelectedValues(): Observable<string[]> {
|
private getSelectedValues(): Observable<string[]> {
|
||||||
return this.filterService.getSelectedValuesForFilter(this.filter);
|
return this.filterService.getSelectedValuesForFilter(this.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +88,7 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
finishSlide(event: any): void {
|
finishSlide(event: any): void {
|
||||||
if (event.fromState === 'collapsed') {
|
if (event.fromState === 'collapsed') {
|
||||||
this.collapsed = false;
|
this.closed = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +98,32 @@ export class SearchFilterComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
startSlide(event: any): void {
|
startSlide(event: any): void {
|
||||||
if (event.toState === 'collapsed') {
|
if (event.toState === 'collapsed') {
|
||||||
this.collapsed = true;
|
this.closed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given filter is supposed to be shown or not
|
||||||
|
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||||
|
*/
|
||||||
|
private isActive(): Observable<boolean> {
|
||||||
|
return this.selectedValues$.pipe(
|
||||||
|
switchMap((isActive) => {
|
||||||
|
if (isNotEmpty(isActive)) {
|
||||||
|
return observableOf(true);
|
||||||
|
} else {
|
||||||
|
return this.searchConfigService.searchOptions.pipe(
|
||||||
|
first(),
|
||||||
|
switchMap((options) => {
|
||||||
|
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
||||||
|
filter((RD) => !RD.isLoading),
|
||||||
|
map((valuesRD) => {
|
||||||
|
return valuesRD.payload.totalElements > 0
|
||||||
|
}),)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
startWith(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
import { SearchFilterAction, SearchFilterActionTypes } from './search-filter.actions';
|
import {
|
||||||
import { isEmpty } from '../../../shared/empty.util';
|
SearchFilterAction,
|
||||||
|
SearchFilterActionTypes,
|
||||||
|
SearchFilterInitializeAction
|
||||||
|
} from './search-filter.actions';
|
||||||
|
import { isEmpty, isNotUndefined } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that represents the state for a single filters
|
* Interface that represents the state for a single filters
|
||||||
@@ -28,27 +32,14 @@ export function filterReducer(state = initialState, action: SearchFilterAction):
|
|||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case SearchFilterActionTypes.INITIAL_COLLAPSE: {
|
case SearchFilterActionTypes.INITIALIZE: {
|
||||||
if (isEmpty(state) || isEmpty(state[action.filterName])) {
|
const initAction = (action as SearchFilterInitializeAction);
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
[action.filterName]: {
|
[action.filterName]: {
|
||||||
filterCollapsed: true,
|
filterCollapsed: !initAction.initiallyExpanded,
|
||||||
page: 1
|
page: 1
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
case SearchFilterActionTypes.INITIAL_EXPAND: {
|
|
||||||
if (isEmpty(state) || isEmpty(state[action.filterName])) {
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
[action.filterName]: {
|
|
||||||
filterCollapsed: false,
|
|
||||||
page: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,8 +5,7 @@ import {
|
|||||||
SearchFilterDecrementPageAction,
|
SearchFilterDecrementPageAction,
|
||||||
SearchFilterExpandAction,
|
SearchFilterExpandAction,
|
||||||
SearchFilterIncrementPageAction,
|
SearchFilterIncrementPageAction,
|
||||||
SearchFilterInitialCollapseAction,
|
SearchFilterInitializeAction,
|
||||||
SearchFilterInitialExpandAction,
|
|
||||||
SearchFilterResetPageAction,
|
SearchFilterResetPageAction,
|
||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from './search-filter.actions';
|
} from './search-filter.actions';
|
||||||
@@ -62,25 +61,16 @@ describe('SearchFilterService', () => {
|
|||||||
service = new SearchFilterService(store, routeServiceStub);
|
service = new SearchFilterService(store, routeServiceStub);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the initialCollapse method is triggered', () => {
|
describe('when the initializeFilter method is triggered', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service.initialCollapse(mockFilterConfig.name);
|
service.initializeFilter(mockFilterConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SearchFilterInitialCollapseAction should be dispatched to the store', () => {
|
it('SearchFilterInitializeAction should be dispatched to the store', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new SearchFilterInitialCollapseAction(mockFilterConfig.name));
|
expect(store.dispatch).toHaveBeenCalledWith(new SearchFilterInitializeAction(mockFilterConfig));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the initialExpand method is triggered', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
service.initialExpand(mockFilterConfig.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('SearchFilterInitialExpandAction should be dispatched to the store', () => {
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new SearchFilterInitialExpandAction(mockFilterConfig.name));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the collapse method is triggered', () => {
|
describe('when the collapse method is triggered', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { Injectable, InjectionToken } from '@angular/core';
|
import { Injectable, InjectionToken } from '@angular/core';
|
||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
|
||||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
SearchFilterDecrementPageAction,
|
SearchFilterDecrementPageAction,
|
||||||
SearchFilterExpandAction,
|
SearchFilterExpandAction,
|
||||||
SearchFilterIncrementPageAction,
|
SearchFilterIncrementPageAction,
|
||||||
SearchFilterInitialCollapseAction,
|
SearchFilterInitializeAction,
|
||||||
SearchFilterInitialExpandAction,
|
|
||||||
SearchFilterResetPageAction,
|
SearchFilterResetPageAction,
|
||||||
SearchFilterToggleAction
|
SearchFilterToggleAction
|
||||||
} from './search-filter.actions';
|
} from './search-filter.actions';
|
||||||
@@ -17,7 +16,9 @@ import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
|
|||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { RouteService } from '../../../shared/services/route.service';
|
import { RouteService } from '../../../shared/services/route.service';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
|
import { tag } from 'rxjs-spy/operators';
|
||||||
|
import { create, detect } from "rxjs-spy";
|
||||||
|
const spy = create();
|
||||||
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||||
|
|
||||||
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
||||||
@@ -58,16 +59,21 @@ export class SearchFilterService {
|
|||||||
* @returns {Observable<string[]>} Emits the active filters for the given filter configuration
|
* @returns {Observable<string[]>} Emits the active filters for the given filter configuration
|
||||||
*/
|
*/
|
||||||
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
||||||
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName);
|
const values$ = this.routeService.getQueryParameterValues(filterConfig.paramName).pipe(
|
||||||
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(
|
tag("parameter")
|
||||||
map((params: Params) => [].concat(...Object.values(params)))
|
|
||||||
);
|
);
|
||||||
|
const prefixValues$ = this.routeService.getQueryParamsWithPrefix(filterConfig.paramName + '.').pipe(
|
||||||
|
map((params: Params) => [].concat(...Object.values(params))),
|
||||||
|
tag("prefix-tag")
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
spy.log();
|
||||||
|
detect('prefix-tag');
|
||||||
|
|
||||||
return observableCombineLatest(values$, prefixValues$).pipe(
|
return observableCombineLatest(values$, prefixValues$).pipe(
|
||||||
map(([values, prefixValues]) => {
|
map(([values, prefixValues]) => {
|
||||||
console.log('getSelectedValuesForFilter ', values, prefixValues);
|
if (isNotEmpty(values)) {
|
||||||
|
|
||||||
if (isNotEmpty(values)) {
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
return prefixValues;
|
return prefixValues;
|
||||||
@@ -138,19 +144,11 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches an initial collapse action to the store for a given filter
|
* Dispatches an initialize action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {SearchFilterConfig} filter The filter for which the action is dispatched
|
||||||
*/
|
*/
|
||||||
public initialCollapse(filterName: string): void {
|
public initializeFilter(filter: SearchFilterConfig): void {
|
||||||
this.store.dispatch(new SearchFilterInitialCollapseAction(filterName));
|
this.store.dispatch(new SearchFilterInitializeAction(filter));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches an initial expand action to the store for a given filter
|
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
|
||||||
*/
|
|
||||||
public initialExpand(filterName: string): void {
|
|
||||||
this.store.dispatch(new SearchFilterInitialExpandAction(filterName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -8,17 +8,8 @@
|
|||||||
</a>
|
</a>
|
||||||
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
||||||
<div [@facetLoad]="animationState">
|
<div [@facetLoad]="animationState">
|
||||||
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value">
|
||||||
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
|
</ds-search-facet-option>
|
||||||
[routerLink]="[getSearchLink()]"
|
|
||||||
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" >
|
|
||||||
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
|
|
||||||
<span class="filter-value px-1">{{value.value}}</span>
|
|
||||||
<span class="float-right filter-value-count ml-auto">
|
|
||||||
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div class="clearfix toggle-more-filters">
|
<div class="clearfix toggle-more-filters">
|
||||||
|
@@ -25,14 +25,7 @@
|
|||||||
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
<ng-container *ngFor="let page of (filterValues$ | async)?.payload">
|
||||||
<div [@facetLoad]="animationState">
|
<div [@facetLoad]="animationState">
|
||||||
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
||||||
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
|
|
||||||
[routerLink]="[getSearchLink()]"
|
|
||||||
[queryParams]="getChangeParams(value.value) | async" queryParamsHandling="merge">
|
|
||||||
<span class="filter-value px-1">{{value.value}}</span>
|
|
||||||
<span class="float-right filter-value-count ml-auto">
|
|
||||||
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -28,10 +28,9 @@ import { SearchConfigurationService } from '../../../search-service/search-confi
|
|||||||
* The route parameter 'id' is used to request the item it represents.
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
* All fields of the item that should be displayed, are defined in its template.
|
* All fields of the item that should be displayed, are defined in its template.
|
||||||
*/
|
*/
|
||||||
const minSuffix = '.min';
|
export const RANGE_FILTER_MIN_SUFFIX = '.min';
|
||||||
const maxSuffix = '.max';
|
export const RANGE_FILTER_MAX_SUFFIX = '.max';
|
||||||
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
|
const dateFormats = ['YYYY', 'YYYY-MM', 'YYYY-MM-DD'];
|
||||||
const rangeDelimiter = '-';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-range-filter',
|
selector: 'ds-search-range-filter',
|
||||||
@@ -85,8 +84,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
|
this.min = moment(this.filterConfig.minValue, dateFormats).year() || this.min;
|
||||||
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
|
this.max = moment(this.filterConfig.maxValue, dateFormats).year() || this.max;
|
||||||
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + minSuffix).pipe(startWith(undefined));
|
const iniMin = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX).pipe(startWith(undefined));
|
||||||
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + maxSuffix).pipe(startWith(undefined));
|
const iniMax = this.route.getQueryParameterValue(this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX).pipe(startWith(undefined));
|
||||||
this.sub = observableCombineLatest(iniMin, iniMax).pipe(
|
this.sub = observableCombineLatest(iniMin, iniMax).pipe(
|
||||||
map(([min, max]) => {
|
map(([min, max]) => {
|
||||||
const minimum = hasValue(min) ? min : this.min;
|
const minimum = hasValue(min) ? min : this.min;
|
||||||
@@ -96,22 +95,7 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
).subscribe((minmax) => this.range = minmax);
|
).subscribe((minmax) => this.range = minmax);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the parameters that should change if a given values for this range filter would be changed
|
|
||||||
* @param {string} value The values that are changed for this filter
|
|
||||||
* @returns {Observable<any>} The changed filter parameters
|
|
||||||
*/
|
|
||||||
getChangeParams(value: string) {
|
|
||||||
const parts = value.split(rangeDelimiter);
|
|
||||||
const min = parts.length > 1 ? parts[0].trim() : value;
|
|
||||||
const max = parts.length > 1 ? parts[1].trim() : value;
|
|
||||||
return observableOf(
|
|
||||||
{
|
|
||||||
[this.filterConfig.paramName + minSuffix]: [min],
|
|
||||||
[this.filterConfig.paramName + maxSuffix]: [max],
|
|
||||||
page: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits new custom range values to the range filter from the widget
|
* Submits new custom range values to the range filter from the widget
|
||||||
@@ -122,8 +106,8 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple
|
|||||||
this.router.navigate([this.getSearchLink()], {
|
this.router.navigate([this.getSearchLink()], {
|
||||||
queryParams:
|
queryParams:
|
||||||
{
|
{
|
||||||
[this.filterConfig.paramName + minSuffix]: newMin,
|
[this.filterConfig.paramName + RANGE_FILTER_MIN_SUFFIX]: newMin,
|
||||||
[this.filterConfig.paramName + maxSuffix]: newMax
|
[this.filterConfig.paramName + RANGE_FILTER_MAX_SUFFIX]: newMax
|
||||||
},
|
},
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
});
|
});
|
||||||
|
@@ -10,15 +10,8 @@
|
|||||||
<div [@facetLoad]="animationState">
|
<div [@facetLoad]="animationState">
|
||||||
<ng-container *ngFor="let page of filterValuesRD?.payload">
|
<ng-container *ngFor="let page of filterValuesRD?.payload">
|
||||||
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
<ng-container *ngFor="let value of page.page; trackBy: trackUpdate">
|
||||||
<a *ngIf="!(selectedValues | async).includes(value.value)" class="d-flex flex-row"
|
<ds-search-facet-option *ngFor="let value of page.page; trackBy: trackUpdate" [filterConfig]="filterConfig" [filterValue]="value">
|
||||||
[routerLink]="[getSearchLink()]"
|
</ds-search-facet-option>
|
||||||
[queryParams]="getAddParams(value.value) | async" queryParamsHandling="merge" >
|
|
||||||
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
|
|
||||||
<span class="filter-value px-1">{{value.value}}</span>
|
|
||||||
<span class="float-right filter-value-count ml-auto">
|
|
||||||
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,6 +33,5 @@
|
|||||||
(submitSuggestion)="onSubmit($event)"
|
(submitSuggestion)="onSubmit($event)"
|
||||||
(clickSuggestion)="onClick($event)"
|
(clickSuggestion)="onClick($event)"
|
||||||
(findSuggestions)="findSuggestions($event)"
|
(findSuggestions)="findSuggestions($event)"
|
||||||
ngDefaultControl
|
ngDefaultControl></ds-input-suggestions>
|
||||||
></ds-input-suggestions>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -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; trackBy: trackUpdate">
|
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
||||||
<ds-search-filter *ngIf="isActive(filter) | async" class="d-block mb-3 p-3" [filter]="filter"></ds-search-filter>
|
<ds-search-filter 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>
|
@@ -1,15 +1,13 @@
|
|||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { filter, first, map, mergeMap, startWith, switchMap, tap } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { SearchService } from '../search-service/search.service';
|
import { SearchService } from '../search-service/search.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
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 { 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';
|
import { SearchFilterService } from './search-filter/search-filter.service';
|
||||||
import { getSucceededRemoteData } from '../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../core/shared/operators';
|
||||||
import { FieldUpdate } from '../../core/data/object-updates/object-updates.reducer';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-filters',
|
selector: 'ds-search-filters',
|
||||||
@@ -53,32 +51,6 @@ export class SearchFiltersComponent {
|
|||||||
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(filterConfig: SearchFilterConfig): Observable<boolean> {
|
|
||||||
return this.filterService.getSelectedValuesForFilter(filterConfig).pipe(
|
|
||||||
switchMap((isActive) => {
|
|
||||||
console.log('selected fires');
|
|
||||||
if (isNotEmpty(isActive)) {
|
|
||||||
return observableOf(true);
|
|
||||||
} else {
|
|
||||||
return this.searchConfigService.searchOptions.pipe(
|
|
||||||
first(),
|
|
||||||
switchMap((options) => {
|
|
||||||
return this.searchService.getFacetValuesFor(filterConfig, 1, options).pipe(
|
|
||||||
filter((RD) => !RD.isLoading),
|
|
||||||
map((valuesRD) => {
|
|
||||||
return valuesRD.payload.totalElements > 0
|
|
||||||
}),)
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}), tap(t => console.log(t)), startWith(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevent unnecessary rerendering
|
* Prevent unnecessary rerendering
|
||||||
*/
|
*/
|
||||||
|
@@ -62,7 +62,6 @@ export class SearchPageComponent implements OnInit {
|
|||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private sidebarService: SearchSidebarService,
|
private sidebarService: SearchSidebarService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
private filterService: SearchFilterService,
|
|
||||||
private searchConfigService: SearchConfigurationService) {
|
private searchConfigService: SearchConfigurationService) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
}
|
}
|
||||||
|
@@ -28,6 +28,9 @@ import { SearchFacetFilterWrapperComponent } from './search-filters/search-filte
|
|||||||
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
||||||
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
|
import { SearchFacetOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component';
|
||||||
|
import { SearchFacetSelectedOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component';
|
||||||
|
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SearchSidebarEffects
|
||||||
@@ -63,6 +66,9 @@ const effects = [
|
|||||||
SearchTextFilterComponent,
|
SearchTextFilterComponent,
|
||||||
SearchHierarchyFilterComponent,
|
SearchHierarchyFilterComponent,
|
||||||
SearchBooleanFilterComponent,
|
SearchBooleanFilterComponent,
|
||||||
|
SearchFacetOptionComponent,
|
||||||
|
SearchFacetSelectedOptionComponent,
|
||||||
|
SearchFacetRangeOptionComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
@@ -82,6 +88,9 @@ const effects = [
|
|||||||
SearchTextFilterComponent,
|
SearchTextFilterComponent,
|
||||||
SearchHierarchyFilterComponent,
|
SearchHierarchyFilterComponent,
|
||||||
SearchBooleanFilterComponent,
|
SearchBooleanFilterComponent,
|
||||||
|
SearchFacetOptionComponent,
|
||||||
|
SearchFacetSelectedOptionComponent,
|
||||||
|
SearchFacetRangeOptionComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { distinctUntilChanged, map } from 'rxjs/operators';
|
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { isNotEmpty } from '../empty.util';
|
import { isNotEmpty } from '../empty.util';
|
||||||
|
import { detect } from 'rxjs-spy';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RouteService {
|
export class RouteService {
|
||||||
@@ -14,6 +15,7 @@ export class RouteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getQueryParameterValues(paramName: string): Observable<string[]> {
|
getQueryParameterValues(paramName: string): Observable<string[]> {
|
||||||
|
console.log('called');
|
||||||
return this.route.queryParamMap.pipe(
|
return this.route.queryParamMap.pipe(
|
||||||
map((params) => [...params.getAll(paramName)]),
|
map((params) => [...params.getAll(paramName)]),
|
||||||
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
|
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
|
||||||
@@ -44,6 +46,7 @@ export class RouteService {
|
|||||||
getQueryParamsWithPrefix(prefix: string): Observable<Params> {
|
getQueryParamsWithPrefix(prefix: string): Observable<Params> {
|
||||||
return this.route.queryParamMap.pipe(
|
return this.route.queryParamMap.pipe(
|
||||||
map((qparams) => {
|
map((qparams) => {
|
||||||
|
console.log('map');
|
||||||
const params = {};
|
const params = {};
|
||||||
qparams.keys
|
qparams.keys
|
||||||
.filter((key) => key.startsWith(prefix))
|
.filter((key) => key.startsWith(prefix))
|
||||||
@@ -52,6 +55,8 @@ export class RouteService {
|
|||||||
});
|
});
|
||||||
return params;
|
return params;
|
||||||
}),
|
}),
|
||||||
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)));
|
distinctUntilChanged((a, b) => { console.log('changed?', a, b, JSON.stringify(a) === JSON.stringify(b)); return JSON.stringify(a) === JSON.stringify(b)}),
|
||||||
|
tap((t) => console.log('changed'))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import { BrowserAppModule } from './modules/app/browser-app.module';
|
|||||||
|
|
||||||
import { ENV_CONFIG } from './config';
|
import { ENV_CONFIG } from './config';
|
||||||
|
|
||||||
|
|
||||||
if (ENV_CONFIG.production) {
|
if (ENV_CONFIG.production) {
|
||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
43
yarn.lock
43
yarn.lock
@@ -243,6 +243,10 @@
|
|||||||
"@types/connect" "*"
|
"@types/connect" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/circular-json@^0.4.0":
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/circular-json/-/circular-json-0.4.0.tgz#7401f7e218cfe87ad4c43690da5658b9acaf51be"
|
||||||
|
|
||||||
"@types/connect@*":
|
"@types/connect@*":
|
||||||
version "3.4.32"
|
version "3.4.32"
|
||||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28"
|
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28"
|
||||||
@@ -370,6 +374,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/stacktrace-js@^0.0.32":
|
||||||
|
version "0.0.32"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/stacktrace-js/-/stacktrace-js-0.0.32.tgz#d23e4a36a5073d39487fbea8234cc6186862d389"
|
||||||
|
|
||||||
"@types/strip-bom@^3.0.0":
|
"@types/strip-bom@^3.0.0":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
|
resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2"
|
||||||
@@ -1600,6 +1608,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
circular-json@^0.5.0:
|
||||||
|
version "0.5.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
|
||||||
|
|
||||||
circular-json@^0.5.5:
|
circular-json@^0.5.5:
|
||||||
version "0.5.5"
|
version "0.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3"
|
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3"
|
||||||
@@ -2605,6 +2617,12 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
|
error-stack-parser@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.2.tgz#4ae8dbaa2bf90a8b450707b9149dcabca135520d"
|
||||||
|
dependencies:
|
||||||
|
stackframe "^1.0.4"
|
||||||
|
|
||||||
es-abstract@^1.4.3, es-abstract@^1.5.1:
|
es-abstract@^1.4.3, es-abstract@^1.5.1:
|
||||||
version "1.12.0"
|
version "1.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
|
||||||
@@ -7237,6 +7255,16 @@ rx@^4.1.0:
|
|||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
|
||||||
|
|
||||||
|
rxjs-spy@^7.5.1:
|
||||||
|
version "7.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/rxjs-spy/-/rxjs-spy-7.5.1.tgz#1a9ef50bc8d7dd00d9ecf3c54c00929231eaf319"
|
||||||
|
dependencies:
|
||||||
|
"@types/circular-json" "^0.4.0"
|
||||||
|
"@types/stacktrace-js" "^0.0.32"
|
||||||
|
circular-json "^0.5.0"
|
||||||
|
error-stack-parser "^2.0.1"
|
||||||
|
stacktrace-gps "^3.0.2"
|
||||||
|
|
||||||
rxjs@6.2.2, rxjs@^6.0.0, rxjs@^6.1.0:
|
rxjs@6.2.2, rxjs@^6.0.0, rxjs@^6.1.0:
|
||||||
version "6.2.2"
|
version "6.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9"
|
||||||
@@ -7681,6 +7709,10 @@ source-map@0.5.0:
|
|||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
|
||||||
|
|
||||||
|
source-map@0.5.6:
|
||||||
|
version "0.5.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||||
|
|
||||||
source-map@0.7.3:
|
source-map@0.7.3:
|
||||||
version "0.7.3"
|
version "0.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||||
@@ -7801,6 +7833,17 @@ ssri@^5.2.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
|
|
||||||
|
stackframe@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b"
|
||||||
|
|
||||||
|
stacktrace-gps@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz#33f8baa4467323ab2bd1816efa279942ba431ccc"
|
||||||
|
dependencies:
|
||||||
|
source-map "0.5.6"
|
||||||
|
stackframe "^1.0.4"
|
||||||
|
|
||||||
static-extend@^0.1.1:
|
static-extend@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||||
|
Reference in New Issue
Block a user