mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 02:24:11 +00:00
facet, filter, pagination implementation using rest api
This commit is contained in:
@@ -18,7 +18,8 @@ module.exports = {
|
|||||||
// Caching settings
|
// Caching settings
|
||||||
cache: {
|
cache: {
|
||||||
// NOTE: how long should objects be cached for by default
|
// NOTE: how long should objects be cached for by default
|
||||||
msToLive: 15 * 60 * 1000, // 15 minute
|
msToLive: 15 * 60 * 1000, // 15 minutes
|
||||||
|
// msToLive: 1000, // 15 minutes
|
||||||
control: 'max-age=60' // revalidate browser
|
control: 'max-age=60' // revalidate browser
|
||||||
},
|
},
|
||||||
// Angular Universal settings
|
// Angular Universal settings
|
||||||
|
@@ -116,6 +116,7 @@
|
|||||||
"@angular/compiler-cli": "^5.2.5",
|
"@angular/compiler-cli": "^5.2.5",
|
||||||
"@ngrx/store-devtools": "^5.1.0",
|
"@ngrx/store-devtools": "^5.1.0",
|
||||||
"@ngtools/webpack": "^1.10.0",
|
"@ngtools/webpack": "^1.10.0",
|
||||||
|
"@types/acorn": "^4.0.3",
|
||||||
"@types/cookie-parser": "1.4.1",
|
"@types/cookie-parser": "1.4.1",
|
||||||
"@types/deep-freeze": "0.1.1",
|
"@types/deep-freeze": "0.1.1",
|
||||||
"@types/express": "^4.11.1",
|
"@types/express": "^4.11.1",
|
||||||
|
@@ -120,6 +120,10 @@
|
|||||||
"dateIssued": {
|
"dateIssued": {
|
||||||
"placeholder": "Date",
|
"placeholder": "Date",
|
||||||
"head": "Date"
|
"head": "Date"
|
||||||
|
},
|
||||||
|
"has_content_in_original_bundle": {
|
||||||
|
"placeholder": "Has files",
|
||||||
|
"head": "Has files"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
src/app/+search-page/paginated-search-options.model.ts
Normal file
20
src/app/+search-page/paginated-search-options.model.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
|
import { isNotEmpty } from '../shared/empty.util';
|
||||||
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
import { SearchOptions } from './search-options.model';
|
||||||
|
|
||||||
|
export class PaginatedSearchOptions extends SearchOptions {
|
||||||
|
pagination?: PaginationComponentOptions;
|
||||||
|
sort?: SortOptions;
|
||||||
|
toRestUrl(url: string, args: string[] = []): string {
|
||||||
|
if (isNotEmpty(this.sort)) {
|
||||||
|
args.push(`sort=${this.sort.field},${this.sort.direction}`);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(this.pagination)) {
|
||||||
|
args.push(`page=${this.pagination.currentPage - 1}`);
|
||||||
|
args.push(`size=${this.pagination.pageSize}`);
|
||||||
|
}
|
||||||
|
return super.toRestUrl(url, args);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,26 +2,29 @@
|
|||||||
<div class="filters">
|
<div class="filters">
|
||||||
<a *ngFor="let value of selectedValues" class="d-block"
|
<a *ngFor="let value of selectedValues" class="d-block"
|
||||||
[routerLink]="[getSearchLink()]"
|
[routerLink]="[getSearchLink()]"
|
||||||
[queryParams]="getQueryParamsWithout(value) | async">
|
[queryParams]="getRemoveParams(value)" queryParamsHandling="merge">
|
||||||
<input type="checkbox" [checked]="true"/>
|
<input type="checkbox" [checked]="true"/>
|
||||||
<span class="filter-value">{{value}}</span>
|
<span class="filter-value">{{value}}</span>
|
||||||
</a>
|
</a>
|
||||||
<a *ngFor="let value of filterValues; let i=index" class="d-block clearfix"
|
<ng-container *ngFor="let page of (filterValues$ | async)">
|
||||||
[routerLink]="[getSearchLink()]"
|
<ng-container *ngFor="let value of (page | async)?.payload.page; let i=index">
|
||||||
[queryParams]="getQueryParamsWith(value.value) | async">
|
<a *ngIf="!selectedValues.includes(value.value)" class="d-block clearfix"
|
||||||
<ng-template [ngIf]="i < (facetCount | async)">
|
[routerLink]="[getSearchLink()]"
|
||||||
|
[queryParams]="getAddParams(value.value)" queryParamsHandling="merge" >
|
||||||
<input type="checkbox" [checked]="false"/>
|
<input type="checkbox" [checked]="false"/>
|
||||||
<span class="filter-value">{{value.value}}</span>
|
<span class="filter-value">{{value.value}}</span>
|
||||||
<span class="float-right filter-value-count">
|
<span class="float-right filter-value-count">
|
||||||
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
<span class="badge badge-secondary badge-pill">{{value.count}}</span>
|
||||||
</span>
|
</span>
|
||||||
</ng-template>
|
</a>
|
||||||
</a>
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
<div class="clearfix toggle-more-filters">
|
<div class="clearfix toggle-more-filters">
|
||||||
<a class="float-left" *ngIf="filterValues.length > (facetCount | async)"
|
<a class="float-left" *ngIf="!(isLastPage() | async)"
|
||||||
(click)="showMore()">{{"search.filters.filter.show-more"
|
(click)="showMore()">{{"search.filters.filter.show-more"
|
||||||
| translate}}</a>
|
| translate}}</a>
|
||||||
<a class="float-right" *ngIf="(currentPage | async) > 1" (click)="showFirstPageOnly()">{{"search.filters.filter.show-less"
|
<a class="float-right" *ngIf="(currentPage | async) > 1"
|
||||||
|
(click)="showFirstPageOnly()">{{"search.filters.filter.show-less"
|
||||||
| translate}}</a>
|
| translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||||
import { Params, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { SearchFilterService } from '../search-filter.service';
|
import { SearchFilterService } from '../search-filter.service';
|
||||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { SearchService } from '../../../search-service/search.service';
|
||||||
|
import { SearchOptions } from '../../../search-options.model';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -15,21 +21,43 @@ import { isNotEmpty } from '../../../../shared/empty.util';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
styleUrls: ['./search-facet-filter.component.scss'],
|
styleUrls: ['./search-facet-filter.component.scss'],
|
||||||
templateUrl: './search-facet-filter.component.html',
|
templateUrl: './search-facet-filter.component.html'
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SearchFacetFilterComponent implements OnInit {
|
export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||||
@Input() filterValues: FacetValue[];
|
|
||||||
@Input() filterConfig: SearchFilterConfig;
|
@Input() filterConfig: SearchFilterConfig;
|
||||||
@Input() selectedValues: string[];
|
@Input() selectedValues: string[];
|
||||||
|
filterValues: Array<Observable<RemoteData<PaginatedList<FacetValue>>>> = [];
|
||||||
|
filterValues$: BehaviorSubject<any> = new BehaviorSubject(this.filterValues);
|
||||||
currentPage: Observable<number>;
|
currentPage: Observable<number>;
|
||||||
filter: string;
|
filter: string;
|
||||||
|
pageChange = false;
|
||||||
|
sub: Subscription;
|
||||||
|
|
||||||
constructor(private filterService: SearchFilterService, private router: Router) {
|
constructor(private searchService: SearchService, private filterService: SearchFilterService, private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.currentPage = this.filterService.getPage(this.filterConfig.name);
|
this.currentPage = this.getCurrentPage();
|
||||||
|
this.currentPage.distinctUntilChanged().subscribe((page) => this.pageChange = true);
|
||||||
|
this.filterService.getSearchOptions().distinctUntilChanged().subscribe((options) => this.updateFilterValueList(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterValueList(options: SearchOptions) {
|
||||||
|
if (!this.pageChange) {
|
||||||
|
this.showFirstPageOnly();
|
||||||
|
}
|
||||||
|
this.pageChange = false;
|
||||||
|
|
||||||
|
this.unsubscribe();
|
||||||
|
|
||||||
|
this.sub = this.currentPage.distinctUntilChanged().map((page) => {
|
||||||
|
return this.searchService.getFacetValuesFor(this.filterConfig, page, options);
|
||||||
|
}).subscribe((newValues$) => {
|
||||||
|
this.filterValues = [...this.filterValues, newValues$];
|
||||||
|
this.filterValues$.next(this.filterValues);
|
||||||
|
});
|
||||||
|
// this.filterValues.subscribe((c) => c.map((a) => a.subscribe((b) => console.log(b))));
|
||||||
}
|
}
|
||||||
|
|
||||||
isChecked(value: FacetValue): Observable<boolean> {
|
isChecked(value: FacetValue): Observable<boolean> {
|
||||||
@@ -37,23 +65,7 @@ export class SearchFacetFilterComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
return this.filterService.searchLink;
|
return this.searchService.getSearchLink();
|
||||||
}
|
|
||||||
|
|
||||||
getQueryParamsWith(value: string): Observable<Params> {
|
|
||||||
return this.filterService.getQueryParamsWith(this.filterConfig, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getQueryParamsWithout(value: string): Observable<Params> {
|
|
||||||
return this.filterService.getQueryParamsWithout(this.filterConfig, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get facetCount(): Observable<number> {
|
|
||||||
const resultCount = this.filterValues.length;
|
|
||||||
return this.currentPage.map((page: number) => {
|
|
||||||
const max = page * this.filterConfig.pageSize;
|
|
||||||
return max > resultCount ? resultCount : max;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showMore() {
|
showMore() {
|
||||||
@@ -61,6 +73,7 @@ export class SearchFacetFilterComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showFirstPageOnly() {
|
showFirstPageOnly() {
|
||||||
|
this.filterValues = [];
|
||||||
this.filterService.resetPage(this.filterConfig.name);
|
this.filterService.resetPage(this.filterConfig.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +87,39 @@ export class SearchFacetFilterComponent implements OnInit {
|
|||||||
|
|
||||||
onSubmit(data: any) {
|
onSubmit(data: any) {
|
||||||
if (isNotEmpty(data)) {
|
if (isNotEmpty(data)) {
|
||||||
const sub = this.getQueryParamsWith(data[this.filterConfig.paramName]).first().subscribe((params) => {
|
this.router.navigate([this.getSearchLink()], {
|
||||||
this.router.navigate([this.getSearchLink()], { queryParams: params }
|
queryParams:
|
||||||
);
|
{ [this.filterConfig.paramName]: [...this.selectedValues, data[this.filterConfig.paramName]] },
|
||||||
}
|
queryParamsHandling: 'merge'
|
||||||
);
|
});
|
||||||
this.filter = '';
|
this.filter = '';
|
||||||
sub.unsubscribe();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasValue(o: any): boolean {
|
||||||
|
return hasValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLastPage(): Observable<boolean> {
|
||||||
|
return Observable.of(false);
|
||||||
|
// return this.filterValues.flatMap((map) => map.pop().map((rd: RemoteData<PaginatedList<FacetValue>>) => rd.payload.currentPage >= rd.payload.totalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemoveParams(value: string) {
|
||||||
|
return { [this.filterConfig.paramName]: this.selectedValues.filter((v) => v !== value) };
|
||||||
|
}
|
||||||
|
|
||||||
|
getAddParams(value: string) {
|
||||||
|
return { [this.filterConfig.paramName]: [...this.selectedValues, value] };
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe(): void {
|
||||||
|
if (this.sub !== undefined) {
|
||||||
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
<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 fa 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 fa float-right"
|
||||||
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
||||||
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" class="search-filter-wrapper">
|
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" class="search-filter-wrapper">
|
||||||
<ds-search-facet-filter [filterConfig]="filter"
|
<ds-search-facet-filter [filterConfig]="filter" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter>
|
||||||
[filterValues]="(filterValues | async)?.payload" [selectedValues]="getSelectedValues() | async"></ds-search-facet-filter>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -23,13 +23,11 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
|||||||
|
|
||||||
export class SearchFilterComponent implements OnInit {
|
export class SearchFilterComponent implements OnInit {
|
||||||
@Input() filter: SearchFilterConfig;
|
@Input() filter: SearchFilterConfig;
|
||||||
filterValues: Observable<RemoteData<FacetValue[] | PaginatedList<FacetValue>>>;
|
|
||||||
|
|
||||||
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
constructor(private filterService: SearchFilterService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.filterValues = this.searchService.getFacetValuesFor(this.filter.name, '', '');
|
|
||||||
const sub = this.filterService.isFilterActive(this.filter.paramName).first().subscribe((isActive) => {
|
const sub = this.filterService.isFilterActive(this.filter.paramName).first().subscribe((isActive) => {
|
||||||
if (this.filter.isOpenByDefault || isActive) {
|
if (this.filter.isOpenByDefault || isActive) {
|
||||||
this.initialExpand();
|
this.initialExpand();
|
||||||
|
@@ -14,6 +14,11 @@ import { hasValue, } from '../../../shared/empty.util';
|
|||||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||||
import { SearchService } from '../../search-service/search.service';
|
import { SearchService } from '../../search-service/search.service';
|
||||||
import { RouteService } from '../../../shared/route.service';
|
import { RouteService } from '../../../shared/route.service';
|
||||||
|
import ObjectExpression from 'rollup/dist/typings/ast/nodes/ObjectExpression';
|
||||||
|
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SearchOptions } from '../../search-options.model';
|
||||||
|
import { PaginatedSearchOptions } from '../../paginated-search-options.model';
|
||||||
|
|
||||||
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||||
|
|
||||||
@@ -21,8 +26,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
|||||||
export class SearchFilterService {
|
export class SearchFilterService {
|
||||||
|
|
||||||
constructor(private store: Store<SearchFiltersState>,
|
constructor(private store: Store<SearchFiltersState>,
|
||||||
private routeService: RouteService,
|
private routeService: RouteService) {
|
||||||
private searchService: SearchService) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isFilterActiveWithValue(paramName: string, filterValue: string): Observable<boolean> {
|
isFilterActiveWithValue(paramName: string, filterValue: string): Observable<boolean> {
|
||||||
@@ -33,22 +37,85 @@ export class SearchFilterService {
|
|||||||
return this.routeService.hasQueryParam(paramName);
|
return this.routeService.hasQueryParam(paramName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryParamsWithout(filterConfig: SearchFilterConfig, value: string) {
|
getCurrentScope() {
|
||||||
return this.routeService.removeQueryParameterValue(filterConfig.paramName, value);
|
return this.routeService.getQueryParameterValue('scope');
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryParamsWith(filterConfig: SearchFilterConfig, value: string) {
|
getCurrentQuery() {
|
||||||
return this.routeService.addQueryParameterValue(filterConfig.paramName, value);
|
return this.routeService.getQueryParameterValue('query');
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentPagination(pagination: any = {}): Observable<PaginationComponentOptions> {
|
||||||
|
const page$ = this.routeService.getQueryParameterValue('page');
|
||||||
|
const size$ = this.routeService.getQueryParameterValue('pageSize');
|
||||||
|
return Observable.combineLatest(page$, size$, (page, size) => {
|
||||||
|
return Object.assign(new PaginationComponentOptions(), pagination, {
|
||||||
|
currentPage: page || 1,
|
||||||
|
pageSize: size || pagination.pageSize
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSort(): Observable<SortOptions> {
|
||||||
|
const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection');
|
||||||
|
const sortField$ = this.routeService.getQueryParameterValue('sortField');
|
||||||
|
return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => new SortOptions(sortField || undefined, SortDirection[sortDirection]));
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentFilters() {
|
||||||
|
return this.routeService.getQueryParamsWithPrefix('f.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentView() {
|
||||||
|
return this.routeService.getQueryParameterValue('view');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPaginatedSearchOptions(defaults: any = {}): Observable<PaginatedSearchOptions> {
|
||||||
|
return Observable.combineLatest(
|
||||||
|
this.getCurrentPagination(defaults.pagination),
|
||||||
|
this.getCurrentSort(),
|
||||||
|
this.getCurrentView(),
|
||||||
|
this.getCurrentScope(),
|
||||||
|
this.getCurrentQuery(),
|
||||||
|
this.getCurrentFilters(),
|
||||||
|
(pagination, sort, view, scope, query, filters) => {
|
||||||
|
return Object.assign(new SearchOptions(),
|
||||||
|
defaults,
|
||||||
|
{
|
||||||
|
pagination: pagination,
|
||||||
|
sort: sort,
|
||||||
|
view: view,
|
||||||
|
scope: scope,
|
||||||
|
query: query,
|
||||||
|
filters: filters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchOptions(defaults: any = {}): Observable<SearchOptions> {
|
||||||
|
return Observable.combineLatest(
|
||||||
|
this.getCurrentView(),
|
||||||
|
this.getCurrentScope(),
|
||||||
|
this.getCurrentQuery(),
|
||||||
|
this.getCurrentFilters(),
|
||||||
|
(view, scope, query, filters) => {
|
||||||
|
return Object.assign(new SearchOptions(),
|
||||||
|
defaults,
|
||||||
|
{
|
||||||
|
view: view,
|
||||||
|
scope: scope,
|
||||||
|
query: query,
|
||||||
|
filters: filters
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
getSelectedValuesForFilter(filterConfig: SearchFilterConfig): Observable<string[]> {
|
||||||
return this.routeService.getQueryParameterValues(filterConfig.paramName);
|
return this.routeService.getQueryParameterValues(filterConfig.paramName);
|
||||||
}
|
}
|
||||||
|
|
||||||
get searchLink() {
|
|
||||||
return this.searchService.uiSearchRoute;
|
|
||||||
}
|
|
||||||
|
|
||||||
isCollapsed(filterName: string): Observable<boolean> {
|
isCollapsed(filterName: string): Observable<boolean> {
|
||||||
return this.store.select(filterByNameSelector(filterName))
|
return this.store.select(filterByNameSelector(filterName))
|
||||||
.map((object: SearchFilterState) => {
|
.map((object: SearchFilterState) => {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<h3>{{"search.filters.head" | translate}}</h3>
|
<h3>{{"search.filters.head" | translate}}</h3>
|
||||||
<div *ngIf="(filters | async).hasSucceeded">
|
<div *ngIf="(filters | async)?.hasSucceeded">
|
||||||
<div *ngFor="let filter of (filters | async).payload">
|
<div *ngFor="let filter of (filters | async).payload">
|
||||||
<ds-search-filter class="d-block mb-3 p-3" [filter]="filter"></ds-search-filter>
|
<ds-search-filter class="d-block mb-3 p-3" [filter]="filter"></ds-search-filter>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
import { isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
export enum ViewMode {
|
export enum ViewMode {
|
||||||
List = 'list',
|
List = 'list',
|
||||||
@@ -7,7 +7,28 @@ export enum ViewMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SearchOptions {
|
export class SearchOptions {
|
||||||
pagination?: PaginationComponentOptions;
|
|
||||||
sort?: SortOptions;
|
|
||||||
view?: ViewMode = ViewMode.List;
|
view?: ViewMode = ViewMode.List;
|
||||||
|
scope?: string;
|
||||||
|
query?: string;
|
||||||
|
filters?: any;
|
||||||
|
|
||||||
|
toRestUrl(url: string, args: string[] = []): string {
|
||||||
|
|
||||||
|
if (isNotEmpty(this.query)) {
|
||||||
|
args.push(`query=${this.query}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNotEmpty(this.scope)) {
|
||||||
|
args.push(`scope=${this.scope}`);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(this.filters)) {
|
||||||
|
Object.entries(this.filters).forEach(([key, values]) => {
|
||||||
|
values.forEach((value) => args.push(`${key}=${value},equals`));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isNotEmpty(args)) {
|
||||||
|
url = new URLCombiner(url, `?${args.join('&')}`).toString();
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ import { SearchOptions, ViewMode } from './search-options.model';
|
|||||||
import { SearchResult } from './search-result.model';
|
import { SearchResult } from './search-result.model';
|
||||||
import { SearchService } from './search-service/search.service';
|
import { SearchService } from './search-service/search.service';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
|
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -36,85 +37,43 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
query: string;
|
query: string;
|
||||||
scopeObjectRDObs: Observable<RemoteData<DSpaceObject>>;
|
scopeObjectRDObs: Observable<RemoteData<DSpaceObject>>;
|
||||||
resultsRDObs: Observable<RemoteData<Array<SearchResult<DSpaceObject>> | PaginatedList<SearchResult<DSpaceObject>>>>;
|
resultsRDObs: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
|
||||||
currentParams = {};
|
currentParams = {};
|
||||||
searchOptions: SearchOptions;
|
searchOptions: SearchOptions;
|
||||||
sortConfig: SortOptions;
|
sortConfig: SortOptions;
|
||||||
scopeListRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
scopeListRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
||||||
isMobileView: Observable<boolean>;
|
isMobileView: Observable<boolean>;
|
||||||
|
pageSize;
|
||||||
|
pageSizeOptions;
|
||||||
|
defaults = {
|
||||||
|
pagination: {
|
||||||
|
id: 'search-results-pagination',
|
||||||
|
pageSize: 10
|
||||||
|
},
|
||||||
|
query: ''
|
||||||
|
};
|
||||||
|
|
||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private route: ActivatedRoute,
|
|
||||||
private communityService: CommunityDataService,
|
private communityService: CommunityDataService,
|
||||||
private sidebarService: SearchSidebarService,
|
private sidebarService: SearchSidebarService,
|
||||||
private windowService: HostWindowService) {
|
private windowService: HostWindowService,
|
||||||
this.isMobileView = Observable.combineLatest(
|
private filterService: SearchFilterService) {
|
||||||
|
this.isMobileView = Observable.combineLatest(
|
||||||
this.windowService.isXs(),
|
this.windowService.isXs(),
|
||||||
this.windowService.isSm(),
|
this.windowService.isSm(),
|
||||||
((isXs, isSm) => isXs || isSm)
|
((isXs, isSm) => isXs || isSm)
|
||||||
);
|
);
|
||||||
this.scopeListRDObs = communityService.findAll();
|
this.scopeListRDObs = communityService.findAll();
|
||||||
// Initial pagination config
|
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
|
||||||
pagination.id = 'search-results-pagination';
|
|
||||||
pagination.currentPage = 1;
|
|
||||||
pagination.pageSize = 10;
|
|
||||||
|
|
||||||
const sort: SortOptions = new SortOptions();
|
|
||||||
this.sortConfig = sort;
|
|
||||||
this.searchOptions = this.service.searchOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.sub = this.route
|
this.sub = this.filterService.getPaginatedSearchOptions(this.defaults).subscribe((options) => {
|
||||||
.queryParams
|
this.updateSearchResults(options);
|
||||||
.subscribe((params) => {
|
});
|
||||||
// Save current parameters
|
|
||||||
this.currentParams = params;
|
|
||||||
this.query = params.query || '';
|
|
||||||
this.scope = params.scope;
|
|
||||||
const page = +params.page || this.searchOptions.pagination.currentPage;
|
|
||||||
let pageSize = +params.pageSize || this.searchOptions.pagination.pageSize;
|
|
||||||
let pageSizeOptions: number[] = [5, 10, 20, 40, 60, 80, 100];
|
|
||||||
|
|
||||||
if (isNotEmpty(params.view) && params.view === ViewMode.Grid) {
|
|
||||||
pageSizeOptions = [12, 24, 36, 48 , 50, 62, 74, 84];
|
|
||||||
if (pageSizeOptions.indexOf(pageSize) === -1) {
|
|
||||||
pageSize = 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNotEmpty(params.view) && params.view === ViewMode.List) {
|
|
||||||
if (pageSizeOptions.indexOf(pageSize) === -1) {
|
|
||||||
pageSize = 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sortDirection = params.sortDirection || this.searchOptions.sort.direction;
|
|
||||||
const sortField = params.sortField || this.searchOptions.sort.field;
|
|
||||||
const pagination = Object.assign({},
|
|
||||||
this.searchOptions.pagination,
|
|
||||||
{ currentPage: page, pageSize: pageSize, pageSizeOptions: pageSizeOptions}
|
|
||||||
);
|
|
||||||
const sort = Object.assign({},
|
|
||||||
this.searchOptions.sort,
|
|
||||||
{ direction: sortDirection, field: sortField }
|
|
||||||
);
|
|
||||||
|
|
||||||
this.updateSearchResults({
|
|
||||||
pagination: pagination,
|
|
||||||
sort: sort
|
|
||||||
});
|
|
||||||
if (isNotEmpty(this.scope)) {
|
|
||||||
this.scopeObjectRDObs = this.communityService.findById(this.scope);
|
|
||||||
} else {
|
|
||||||
this.scopeObjectRDObs = Observable.of(undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSearchResults(searchOptions) {
|
private updateSearchResults(searchOptions) {
|
||||||
this.resultsRDObs = this.service.search(this.query, this.scope, searchOptions);
|
this.resultsRDObs = this.service.search(searchOptions);
|
||||||
this.searchOptions = searchOptions;
|
this.searchOptions = searchOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
export enum FilterType {
|
export enum FilterType {
|
||||||
text,
|
text,
|
||||||
range,
|
date,
|
||||||
hierarchy
|
hierarchical,
|
||||||
|
standard
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,27 @@
|
|||||||
import { FilterType } from './filter-type.model';
|
import { FilterType } from './filter-type.model';
|
||||||
|
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||||
|
|
||||||
export class SearchFilterConfig {
|
export class SearchFilterConfig {
|
||||||
|
|
||||||
name: string;
|
@autoserialize
|
||||||
type: FilterType;
|
name: string;
|
||||||
hasFacets: boolean;
|
|
||||||
pageSize = 5;
|
@autoserializeAs(String, 'facetType')
|
||||||
isOpenByDefault: boolean;
|
type: FilterType;
|
||||||
/**
|
|
||||||
* Name of this configuration that can be used in a url
|
@autoserialize
|
||||||
* @returns Parameter name
|
hasFacets: boolean;
|
||||||
*/
|
|
||||||
get paramName(): string {
|
// @autoserializeAs(String, 'facetLimit') - uncomment when fixed in rest
|
||||||
return 'f.' + this.name;
|
pageSize = 5;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
isOpenByDefault: boolean;
|
||||||
|
/**
|
||||||
|
* Name of this configuration that can be used in a url
|
||||||
|
* @returns Parameter name
|
||||||
|
*/
|
||||||
|
get paramName(): string {
|
||||||
|
return 'f.' + this.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -6,7 +6,8 @@ import { ViewMode } from '../../+search-page/search-options.model';
|
|||||||
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
||||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import {
|
import {
|
||||||
FacetValueMapSuccessResponse, FacetValueSuccessResponse,
|
FacetConfigSuccessResponse,
|
||||||
|
FacetValueSuccessResponse,
|
||||||
SearchSuccessResponse
|
SearchSuccessResponse
|
||||||
} from '../../core/cache/response-cache.models';
|
} from '../../core/cache/response-cache.models';
|
||||||
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
|
import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
|
||||||
@@ -34,26 +35,17 @@ import { SearchQueryResponse } from './search-query-response.model';
|
|||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
import { getSearchResultFor } from './search-result-element-decorator';
|
import { getSearchResultFor } from './search-result-element-decorator';
|
||||||
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
|
||||||
import { FacetResponseParsingService } from '../../core/data/facet-response-parsing.service';
|
import { FacetValueResponseParsingService } from '../../core/data/facet-value-response-parsing.service';
|
||||||
|
import { FacetConfigResponseParsingService } from '../../core/data/facet-config-response-parsing.service';
|
||||||
function shuffle(array: any[]) {
|
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
||||||
let i = 0;
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
let j = 0;
|
|
||||||
let temp = null;
|
|
||||||
|
|
||||||
for (i = array.length - 1; i > 0; i -= 1) {
|
|
||||||
j = Math.floor(Math.random() * (i + 1));
|
|
||||||
temp = array[i];
|
|
||||||
array[i] = array[j];
|
|
||||||
array[j] = temp;
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService implements OnDestroy {
|
export class SearchService implements OnDestroy {
|
||||||
private searchLinkPath = 'discover/search/objects';
|
private searchLinkPath = 'discover/search/objects';
|
||||||
private facetLinkPath = 'discover/search/facets';
|
private facetValueLinkPath = 'discover/search/facets';
|
||||||
|
private facetValueLinkPathPrefix = 'discover/facets/';
|
||||||
|
private facetConfigLinkPath = 'discover/facets';
|
||||||
|
|
||||||
private sub;
|
private sub;
|
||||||
uiSearchRoute = '/search';
|
uiSearchRoute = '/search';
|
||||||
@@ -62,7 +54,7 @@ export class SearchService implements OnDestroy {
|
|||||||
// Object.assign(new SearchFilterConfig(),
|
// Object.assign(new SearchFilterConfig(),
|
||||||
// {
|
// {
|
||||||
// name: 'scope',
|
// name: 'scope',
|
||||||
// type: FilterType.hierarchy,
|
// type: FilterType.hierarchical,
|
||||||
// hasFacets: true,
|
// hasFacets: true,
|
||||||
// isOpenByDefault: true
|
// isOpenByDefault: true
|
||||||
// }),
|
// }),
|
||||||
@@ -76,7 +68,7 @@ export class SearchService implements OnDestroy {
|
|||||||
Object.assign(new SearchFilterConfig(),
|
Object.assign(new SearchFilterConfig(),
|
||||||
{
|
{
|
||||||
name: 'dateIssued',
|
name: 'dateIssued',
|
||||||
type: FilterType.range,
|
type: FilterType.date,
|
||||||
hasFacets: true,
|
hasFacets: true,
|
||||||
isOpenByDefault: false
|
isOpenByDefault: false
|
||||||
}),
|
}),
|
||||||
@@ -95,7 +87,6 @@ export class SearchService implements OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
protected responseCache: ResponseCacheService,
|
protected responseCache: ResponseCacheService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
private routeService: RouteService,
|
|
||||||
private rdb: RemoteDataBuildService,
|
private rdb: RemoteDataBuildService,
|
||||||
private halService: HALEndpointService) {
|
private halService: HALEndpointService) {
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
@@ -103,36 +94,15 @@ export class SearchService implements OnDestroy {
|
|||||||
pagination.currentPage = 1;
|
pagination.currentPage = 1;
|
||||||
pagination.pageSize = 10;
|
pagination.pageSize = 10;
|
||||||
const sort: SortOptions = new SortOptions();
|
const sort: SortOptions = new SortOptions();
|
||||||
this.searchOptions = { pagination: pagination, sort: sort };
|
this.searchOptions = Object.assign(new SearchOptions(), { pagination: pagination, sort: sort });
|
||||||
// this.searchOptions = new BehaviorSubject<SearchOptions>(searchOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>> | PaginatedList<SearchResult<DSpaceObject>>>> {
|
search(searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
||||||
const requestObs = this.halService.getEndpoint(this.searchLinkPath).pipe(
|
const requestObs = this.halService.getEndpoint(this.searchLinkPath).pipe(
|
||||||
map((url: string) => {
|
map((url: string) => {
|
||||||
const args: string[] = [];
|
if (hasValue(searchOptions)) {
|
||||||
|
url = searchOptions.toRestUrl(url);
|
||||||
if (isNotEmpty(query)) {
|
|
||||||
args.push(`query=${query}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotEmpty(scopeId)) {
|
|
||||||
args.push(`scope=${scopeId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotEmpty(searchOptions)) {
|
|
||||||
if (isNotEmpty(searchOptions.sort)) {
|
|
||||||
args.push(`sort=${searchOptions.sort.field},${searchOptions.sort.direction}`);
|
|
||||||
}
|
|
||||||
if (isNotEmpty(searchOptions.pagination)) {
|
|
||||||
args.push(`page=${searchOptions.pagination.currentPage - 1}`);
|
|
||||||
args.push(`size=${searchOptions.pagination.pageSize}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isNotEmpty(args)) {
|
|
||||||
url = new URLCombiner(url, `?${args.join('&')}`).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
||||||
return Object.assign(request, {
|
return Object.assign(request, {
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
@@ -183,55 +153,25 @@ export class SearchService implements OnDestroy {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageInfoObs: Observable<PageInfo> = responseCacheObs
|
const pageInfoObs: Observable<PageInfo> = responseCacheObs.pipe(
|
||||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
map((entry: ResponseCacheEntry) => entry.response),
|
||||||
.map((entry: ResponseCacheEntry) => {
|
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
||||||
if (hasValue((entry.response as SearchSuccessResponse).pageInfo)) {
|
);
|
||||||
const resPageInfo = (entry.response as SearchSuccessResponse).pageInfo;
|
|
||||||
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
|
|
||||||
return Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
|
|
||||||
} else {
|
|
||||||
return resPageInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => {
|
const payloadObs = Observable.combineLatest(tDomainListObs, pageInfoObs, (tDomainList, pageInfo) => {
|
||||||
if (hasValue(pageInfo)) {
|
return new PaginatedList(pageInfo, tDomainList);
|
||||||
return new PaginatedList(pageInfo, tDomainList);
|
|
||||||
} else {
|
|
||||||
return tDomainList;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig(): Observable<RemoteData<SearchFilterConfig[]>> {
|
getConfig(scope?: string): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||||
const requestPending = false;
|
const requestObs = this.halService.getEndpoint(this.facetConfigLinkPath).pipe(
|
||||||
const responsePending = false;
|
|
||||||
const isSuccessful = true;
|
|
||||||
const error = undefined;
|
|
||||||
return Observable.of(new RemoteData(
|
|
||||||
requestPending,
|
|
||||||
responsePending,
|
|
||||||
isSuccessful,
|
|
||||||
error,
|
|
||||||
this.config
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
getFacetValuesFor(searchFilterConfigName: string, query: string, scopeId: string): Observable<RemoteData<FacetValue[] | PaginatedList<FacetValue>>> {
|
|
||||||
const requestObs = this.halService.getEndpoint(this.facetLinkPath).pipe(
|
|
||||||
map((url: string) => {
|
map((url: string) => {
|
||||||
const args: string[] = [];
|
const args: string[] = [];
|
||||||
|
|
||||||
if (isNotEmpty(query)) {
|
if (isNotEmpty(scope)) {
|
||||||
args.push(`query=${query}`);
|
args.push(`scope=${scope}`);
|
||||||
}
|
|
||||||
|
|
||||||
if (isNotEmpty(scopeId)) {
|
|
||||||
args.push(`scope=${scopeId}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNotEmpty(args)) {
|
if (isNotEmpty(args)) {
|
||||||
@@ -241,7 +181,7 @@ export class SearchService implements OnDestroy {
|
|||||||
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
||||||
return Object.assign(request, {
|
return Object.assign(request, {
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
return FacetResponseParsingService;
|
return FacetConfigResponseParsingService;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -257,56 +197,56 @@ export class SearchService implements OnDestroy {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const facetValueResponseObs: Observable<FacetValueSuccessResponse> = responseCacheObs.pipe(
|
const facetConfigObs: Observable<SearchFilterConfig[]> = responseCacheObs.pipe(
|
||||||
map((entry: ResponseCacheEntry) => entry.response),
|
map((entry: ResponseCacheEntry) => entry.response),
|
||||||
map((response: FacetValueMapSuccessResponse) => response.results[searchFilterConfigName])
|
map((response: FacetConfigSuccessResponse) => response.results)
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, facetConfigObs);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions): Observable<RemoteData<PaginatedList<FacetValue>>> {
|
||||||
|
console.log('facetvalues');
|
||||||
|
const requestObs = this.halService.getEndpoint(this.facetValueLinkPathPrefix + filterConfig.name).pipe(
|
||||||
|
map((url: string) => {
|
||||||
|
const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`];
|
||||||
|
if (hasValue(searchOptions)) {
|
||||||
|
url = searchOptions.toRestUrl(url, args);
|
||||||
|
}
|
||||||
|
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
||||||
|
return Object.assign(request, {
|
||||||
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
return FacetValueResponseParsingService;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
tap((request: RestRequest) => this.requestService.configure(request)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestEntryObs = requestObs.pipe(
|
||||||
|
flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseCacheObs = requestObs.pipe(
|
||||||
|
flatMap((request: RestRequest) => this.responseCache.get(request.href))
|
||||||
);
|
);
|
||||||
|
|
||||||
// get search results from response cache
|
// get search results from response cache
|
||||||
const facetValueObs: Observable<FacetValue[]> = facetValueResponseObs.pipe(
|
const facetValueObs: Observable<FacetValue[]> = responseCacheObs.pipe(
|
||||||
|
map((entry: ResponseCacheEntry) => entry.response),
|
||||||
map((response: FacetValueSuccessResponse) => response.results)
|
map((response: FacetValueSuccessResponse) => response.results)
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageInfoObs: Observable<PageInfo> = facetValueResponseObs.pipe(
|
const pageInfoObs: Observable<PageInfo> = responseCacheObs.pipe(
|
||||||
map((response: FacetValueSuccessResponse) => { console.log(response); return response.pageInfo})
|
map((entry: ResponseCacheEntry) => entry.response),
|
||||||
|
map((response: FacetValueSuccessResponse) => response.pageInfo)
|
||||||
);
|
);
|
||||||
const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => {
|
|
||||||
if (hasValue(pageInfo)) {
|
|
||||||
return new PaginatedList(pageInfo, facetValue);
|
|
||||||
} else {
|
|
||||||
return facetValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
|
||||||
|
|
||||||
// const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === searchFilterConfigName);
|
const payloadObs = Observable.combineLatest(facetValueObs, pageInfoObs, (facetValue, pageInfo) => {
|
||||||
// return this.routeService.getQueryParameterValues(filterConfig.paramName).map((selectedValues: string[]) => {
|
return new PaginatedList(pageInfo, facetValue);
|
||||||
// const payload: FacetValue[] = [];
|
});
|
||||||
// const totalFilters = 13;
|
|
||||||
// for (let i = 0; i < totalFilters; i++) {
|
return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
|
||||||
// const value = searchFilterConfigName + ' ' + (i + 1);
|
|
||||||
// if (!selectedValues.includes(value)) {
|
|
||||||
// payload.push({
|
|
||||||
// value: value,
|
|
||||||
// count: Math.floor(Math.random() * 20) + 20 * (totalFilters - i), // make sure first results have the highest (random) count
|
|
||||||
// search: (decodeURI(this.router.url) + (this.router.url.includes('?') ? '&' : '?') + filterConfig.paramName + '=' + value)
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// const requestPending = false;
|
|
||||||
// const responsePending = false;
|
|
||||||
// const isSuccessful = true;
|
|
||||||
// const error = undefined;
|
|
||||||
// return new RemoteData(
|
|
||||||
// requestPending,
|
|
||||||
// responsePending,
|
|
||||||
// isSuccessful,
|
|
||||||
// error,
|
|
||||||
// payload
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewMode(): Observable<ViewMode> {
|
getViewMode(): Observable<ViewMode> {
|
||||||
|
@@ -3,6 +3,7 @@ import { SearchService } from '../search-service/search.service';
|
|||||||
import { SearchOptions, ViewMode } from '../search-options.model';
|
import { SearchOptions, ViewMode } from '../search-options.model';
|
||||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
|
import { PaginatedSearchOptions } from '../paginated-search-options.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-settings',
|
selector: 'ds-search-settings',
|
||||||
@@ -11,7 +12,7 @@ import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
|||||||
})
|
})
|
||||||
export class SearchSettingsComponent implements OnInit {
|
export class SearchSettingsComponent implements OnInit {
|
||||||
|
|
||||||
@Input() searchOptions: SearchOptions;
|
@Input() searchOptions: PaginatedSearchOptions;
|
||||||
/**
|
/**
|
||||||
* Declare SortDirection enumeration to use it in the template
|
* Declare SortDirection enumeration to use it in the template
|
||||||
*/
|
*/
|
||||||
|
10
src/app/core/cache/response-cache.models.ts
vendored
10
src/app/core/cache/response-cache.models.ts
vendored
@@ -4,6 +4,7 @@ import { PageInfo } from '../shared/page-info.model';
|
|||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
import { ConfigObject } from '../shared/config/config.model';
|
import { ConfigObject } from '../shared/config/config.model';
|
||||||
import { FacetValue } from '../../+search-page/search-service/facet-value.model';
|
import { FacetValue } from '../../+search-page/search-service/facet-value.model';
|
||||||
|
import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
export class RestResponse {
|
export class RestResponse {
|
||||||
@@ -33,6 +34,15 @@ export class SearchSuccessResponse extends RestResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class FacetConfigSuccessResponse extends RestResponse {
|
||||||
|
constructor(
|
||||||
|
public results: SearchFilterConfig[],
|
||||||
|
public statusCode: string
|
||||||
|
) {
|
||||||
|
super(true, statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FacetValueMap {
|
export class FacetValueMap {
|
||||||
[name: string]: FacetValueSuccessResponse
|
[name: string]: FacetValueSuccessResponse
|
||||||
}
|
}
|
||||||
|
@@ -8,5 +8,5 @@ export const coreEffects = [
|
|||||||
ResponseCacheEffects,
|
ResponseCacheEffects,
|
||||||
RequestEffects,
|
RequestEffects,
|
||||||
ObjectCacheEffects,
|
ObjectCacheEffects,
|
||||||
UUIDIndexEffects,
|
UUIDIndexEffects
|
||||||
];
|
];
|
||||||
|
@@ -41,7 +41,9 @@ import { SubmissionFormsConfigService } from './config/submission-forms-config.s
|
|||||||
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
|
||||||
import { UUIDService } from './shared/uuid.service';
|
import { UUIDService } from './shared/uuid.service';
|
||||||
import { HALEndpointService } from './shared/hal-endpoint.service';
|
import { HALEndpointService } from './shared/hal-endpoint.service';
|
||||||
import { FacetResponseParsingService } from './data/facet-response-parsing.service';
|
import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service';
|
||||||
|
import { FacetValueMapResponseParsingService } from './data/facet-value-map-response-parsing.service';
|
||||||
|
import { FacetConfigResponseParsingService } from './data/facet-config-response-parsing.service';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -73,7 +75,9 @@ const PROVIDERS = [
|
|||||||
RequestService,
|
RequestService,
|
||||||
ResponseCacheService,
|
ResponseCacheService,
|
||||||
EndpointMapResponseParsingService,
|
EndpointMapResponseParsingService,
|
||||||
FacetResponseParsingService,
|
FacetValueResponseParsingService,
|
||||||
|
FacetValueMapResponseParsingService,
|
||||||
|
FacetConfigResponseParsingService,
|
||||||
DebugResponseParsingService,
|
DebugResponseParsingService,
|
||||||
SearchResponseParsingService,
|
SearchResponseParsingService,
|
||||||
ServerResponseService,
|
ServerResponseService,
|
||||||
|
@@ -117,9 +117,13 @@ export abstract class BaseResponseParsingService {
|
|||||||
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
|
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processPageInfo(pageObj: any): PageInfo {
|
processPageInfo(pageObj: any): PageInfo {
|
||||||
if (isNotEmpty(pageObj)) {
|
if (isNotEmpty(pageObj)) {
|
||||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
const pageInfoObject = new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
||||||
|
if (pageInfoObject.currentPage >= 0) {
|
||||||
|
Object.assign(pageInfoObject, { currentPage: pageInfoObject.currentPage + 1 });
|
||||||
|
}
|
||||||
|
return pageInfoObject
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
32
src/app/core/data/facet-config-response-parsing.service.ts
Normal file
32
src/app/core/data/facet-config-response-parsing.service.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FacetConfigSuccessResponse,
|
||||||
|
RestResponse
|
||||||
|
} from '../cache/response-cache.models';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { RestRequest } from './request.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
|
import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model';
|
||||||
|
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FacetConfigResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
objectFactory = {};
|
||||||
|
toCache = false;
|
||||||
|
constructor(
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) { super();
|
||||||
|
}
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
|
||||||
|
const config = data.payload._embedded.facets;
|
||||||
|
const serializer = new DSpaceRESTv2Serializer(SearchFilterConfig);
|
||||||
|
const facetConfig = serializer.deserializeArray(config);
|
||||||
|
return new FacetConfigSuccessResponse(facetConfig, data.statusCode);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FacetValueMap,
|
FacetValueMap,
|
||||||
FacetValueMapSuccessResponse,
|
FacetValueMapSuccessResponse,
|
||||||
@@ -12,9 +12,22 @@ import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.seriali
|
|||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { FacetValue } from '../../+search-page/search-service/facet-value.model';
|
import { FacetValue } from '../../+search-page/search-service/facet-value.model';
|
||||||
|
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FacetResponseParsingService implements ResponseParsingService {
|
export class FacetValueMapResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
objectFactory = {};
|
||||||
|
toCache = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) { super();
|
||||||
|
}
|
||||||
|
|
||||||
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
|
||||||
const payload = data.payload;
|
const payload = data.payload;
|
||||||
@@ -30,12 +43,4 @@ export class FacetResponseParsingService implements ResponseParsingService {
|
|||||||
|
|
||||||
return new FacetValueMapSuccessResponse(facetMap, data.statusCode);
|
return new FacetValueMapSuccessResponse(facetMap, data.statusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processPageInfo(pageObj: any): PageInfo {
|
|
||||||
if (isNotEmpty(pageObj)) {
|
|
||||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
38
src/app/core/data/facet-value-response-parsing.service.ts
Normal file
38
src/app/core/data/facet-value-response-parsing.service.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FacetValueMap,
|
||||||
|
FacetValueMapSuccessResponse,
|
||||||
|
FacetValueSuccessResponse,
|
||||||
|
RestResponse
|
||||||
|
} from '../cache/response-cache.models';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { RestRequest } from './request.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { FacetValue } from '../../+search-page/search-service/facet-value.model';
|
||||||
|
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FacetValueResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
objectFactory = {};
|
||||||
|
toCache = false;
|
||||||
|
constructor(
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) { super();
|
||||||
|
}
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
const payload = data.payload;
|
||||||
|
|
||||||
|
const serializer = new DSpaceRESTv2Serializer(FacetValue);
|
||||||
|
const values = payload._embedded.values.map((value) => {value.search = value._links.search.href; return value;});
|
||||||
|
|
||||||
|
const facetValues = serializer.deserializeArray(values);
|
||||||
|
return new FacetValueSuccessResponse(facetValues, data.statusCode, this.processPageInfo(data.payload.page));
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,17 @@
|
|||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
|
||||||
export class PaginatedList<T> {
|
export class PaginatedList<T> {
|
||||||
|
|
||||||
constructor(
|
constructor(private pageInfo: PageInfo,
|
||||||
private pageInfo: PageInfo,
|
public page: T[]) {
|
||||||
public page: T[]
|
|
||||||
) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get elementsPerPage(): number {
|
get elementsPerPage(): number {
|
||||||
return this.pageInfo.elementsPerPage;
|
if (hasValue(this.pageInfo)) {
|
||||||
|
return this.pageInfo.elementsPerPage;
|
||||||
|
}
|
||||||
|
return this.page.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
set elementsPerPage(value: number) {
|
set elementsPerPage(value: number) {
|
||||||
@@ -17,7 +19,10 @@ export class PaginatedList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get totalElements(): number {
|
get totalElements(): number {
|
||||||
return this.pageInfo.totalElements;
|
if (hasValue(this.pageInfo)) {
|
||||||
|
return this.pageInfo.totalElements;
|
||||||
|
}
|
||||||
|
return this.page.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
set totalElements(value: number) {
|
set totalElements(value: number) {
|
||||||
@@ -25,7 +30,10 @@ export class PaginatedList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get totalPages(): number {
|
get totalPages(): number {
|
||||||
return this.pageInfo.totalPages;
|
if (hasValue(this.pageInfo)) {
|
||||||
|
return this.pageInfo.totalPages;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
set totalPages(value: number) {
|
set totalPages(value: number) {
|
||||||
@@ -33,7 +41,11 @@ export class PaginatedList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currentPage(): number {
|
get currentPage(): number {
|
||||||
return this.pageInfo.currentPage;
|
if (hasValue(this.pageInfo)) {
|
||||||
|
return this.pageInfo.currentPage;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set currentPage(value: number) {
|
set currentPage(value: number) {
|
||||||
|
@@ -56,14 +56,6 @@ export class SearchResponseParsingService implements ResponseParsingService {
|
|||||||
}));
|
}));
|
||||||
payload.objects = objects;
|
payload.objects = objects;
|
||||||
const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
|
const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
|
||||||
return new SearchSuccessResponse(deserialized, data.statusCode, this.processPageInfo(data.payload.page));
|
return new SearchSuccessResponse(deserialized, data.statusCode, this.dsoParser.processPageInfo(data.payload.page));
|
||||||
}
|
|
||||||
|
|
||||||
protected processPageInfo(pageObj: any): PageInfo {
|
|
||||||
if (isNotEmpty(pageObj)) {
|
|
||||||
return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { distinctUntilChanged, map, flatMap, startWith } from 'rxjs/operators';
|
import { distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response-cache.models';
|
import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response-cache.models';
|
||||||
import { EndpointMapRequest } from '../data/request.models';
|
import { EndpointMapRequest } from '../data/request.models';
|
||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { GLOBAL_CONFIG } from '../../../config';
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
@@ -21,6 +21,7 @@ export class HALEndpointService {
|
|||||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getRootHref(): string {
|
protected getRootHref(): string {
|
||||||
return new RESTURLCombiner(this.EnvConfig, '/').toString();
|
return new RESTURLCombiner(this.EnvConfig, '/').toString();
|
||||||
}
|
}
|
||||||
@@ -34,23 +35,35 @@ export class HALEndpointService {
|
|||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
return this.responseCache.get(request.href)
|
return this.responseCache.get(request.href)
|
||||||
.map((entry: ResponseCacheEntry) => entry.response)
|
.map((entry: ResponseCacheEntry) => entry.response)
|
||||||
.filter((response: EndpointMapSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
.filter((response: EndpointMapSuccessResponse) => isNotEmpty(response))
|
||||||
.map((response: EndpointMapSuccessResponse) => response.endpointMap)
|
.map((response: EndpointMapSuccessResponse) => response.endpointMap)
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEndpoint(linkPath: string): Observable<string> {
|
public getEndpoint(linkPath: string): Observable<string> {
|
||||||
return this.getEndpointAt(...linkPath.split('/'));
|
const test = this.getEndpointAt(...linkPath.split('/'));
|
||||||
|
// test.subscribe((test) => console.log(linkPath, test));
|
||||||
|
return test;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEndpointAt(...path: string[]): Observable<string> {
|
private getEndpointAt(...path: string[]): Observable<string> {
|
||||||
if (isEmpty(path)) {
|
if (isEmpty(path)) {
|
||||||
path = ['/'];
|
path = ['/'];
|
||||||
}
|
}
|
||||||
|
let currentPath;
|
||||||
const pipeArguments = path
|
const pipeArguments = path
|
||||||
.map((subPath: string) => [
|
.map((subPath: string, index: number) => [
|
||||||
flatMap((href: string) => this.getEndpointMapAt(href)),
|
flatMap((href: string) => this.getEndpointMapAt(href)),
|
||||||
map((endpointMap: EndpointMap) => endpointMap[subPath]),
|
map((endpointMap: EndpointMap) => {
|
||||||
|
if (hasValue(endpointMap) && hasValue(endpointMap[subPath])) {
|
||||||
|
currentPath = endpointMap[subPath];
|
||||||
|
return endpointMap[subPath];
|
||||||
|
} else {
|
||||||
|
/*TODO remove if/else block once the rest response contains _links for facets*/
|
||||||
|
currentPath += '/' + subPath;
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
.reduce((combined, thisElement) => [...combined, ...thisElement], []);
|
.reduce((combined, thisElement) => [...combined, ...thisElement], []);
|
||||||
return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged());
|
return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged());
|
||||||
|
@@ -69,12 +69,12 @@ describe('RouteService', () => {
|
|||||||
|
|
||||||
describe('addQueryParameterValue', () => {
|
describe('addQueryParameterValue', () => {
|
||||||
it('should return a list of values that contains the added value when a new value is added and the parameter did not exist yet', () => {
|
it('should return a list of values that contains the added value when a new value is added and the parameter did not exist yet', () => {
|
||||||
service.addQueryParameterValue(nonExistingParamName, nonExistingParamValue).subscribe((params) => {
|
service.resolveRouteWithParameterValue(nonExistingParamName, nonExistingParamValue).subscribe((params) => {
|
||||||
expect(params[nonExistingParamName]).toContain(nonExistingParamValue);
|
expect(params[nonExistingParamName]).toContain(nonExistingParamValue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should return a list of values that contains the existing values and the added value when a new value is added and the parameter already has values', () => {
|
it('should return a list of values that contains the existing values and the added value when a new value is added and the parameter already has values', () => {
|
||||||
service.addQueryParameterValue(paramName1, nonExistingParamValue).subscribe((params) => {
|
service.resolveRouteWithParameterValue(paramName1, nonExistingParamValue).subscribe((params) => {
|
||||||
const values = params[paramName1];
|
const values = params[paramName1];
|
||||||
expect(values).toContain(paramValue1);
|
expect(values).toContain(paramValue1);
|
||||||
expect(values).toContain(nonExistingParamValue);
|
expect(values).toContain(nonExistingParamValue);
|
||||||
@@ -84,7 +84,7 @@ describe('RouteService', () => {
|
|||||||
|
|
||||||
describe('removeQueryParameterValue', () => {
|
describe('removeQueryParameterValue', () => {
|
||||||
it('should return a list of values that does not contain the removed value when the parameter value exists', () => {
|
it('should return a list of values that does not contain the removed value when the parameter value exists', () => {
|
||||||
service.removeQueryParameterValue(paramName2, paramValue2a).subscribe((params) => {
|
service.resolveRouteWithoutParameterValue(paramName2, paramValue2a).subscribe((params) => {
|
||||||
const values = params[paramName2];
|
const values = params[paramName2];
|
||||||
expect(values).toContain(paramValue2b);
|
expect(values).toContain(paramValue2b);
|
||||||
expect(values).not.toContain(paramValue2a);
|
expect(values).not.toContain(paramValue2a);
|
||||||
@@ -92,7 +92,7 @@ describe('RouteService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of values that does contain all existing values when the removed parameter does not exist', () => {
|
it('should return a list of values that does contain all existing values when the removed parameter does not exist', () => {
|
||||||
service.removeQueryParameterValue(paramName2, nonExistingParamValue).subscribe((params) => {
|
service.resolveRouteWithoutParameterValue(paramName2, nonExistingParamValue).subscribe((params) => {
|
||||||
const values = params[paramName2];
|
const values = params[paramName2];
|
||||||
expect(values).toContain(paramValue2a);
|
expect(values).toContain(paramValue2a);
|
||||||
expect(values).toContain(paramValue2b);
|
expect(values).toContain(paramValue2b);
|
||||||
@@ -100,15 +100,15 @@ describe('RouteService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removeQueryParameter', () => {
|
describe('getWithoutParameter', () => {
|
||||||
it('should return a list of values that does not contain any values for the parameter anymore when the parameter exists', () => {
|
it('should return a list of values that does not contain any values for the parameter anymore when the parameter exists', () => {
|
||||||
service.removeQueryParameter(paramName2).subscribe((params) => {
|
service.resolveRouteWithoutParameter(paramName2).subscribe((params) => {
|
||||||
const values = params[paramName2];
|
const values = params[paramName2];
|
||||||
expect(values).toEqual({});
|
expect(values).toEqual({});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should return a list of values that does not contain any values for the parameter when the parameter does not exist', () => {
|
it('should return a list of values that does not contain any values for the parameter when the parameter does not exist', () => {
|
||||||
service.removeQueryParameter(nonExistingParamName).subscribe((params) => {
|
service.resolveRouteWithoutParameter(nonExistingParamName).subscribe((params) => {
|
||||||
const values = params[nonExistingParamName];
|
const values = params[nonExistingParamName];
|
||||||
expect(values).toEqual({});
|
expect(values).toEqual({});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { ActivatedRoute, convertToParamMap, Params, } from '@angular/router';
|
import {
|
||||||
|
ActivatedRoute, convertToParamMap, NavigationExtras, Params,
|
||||||
|
Router,
|
||||||
|
} from '@angular/router';
|
||||||
import { isNotEmpty } from './empty.util';
|
import { isNotEmpty } from './empty.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -10,7 +13,7 @@ export class RouteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getQueryParameterValues(paramName: string): Observable<string[]> {
|
getQueryParameterValues(paramName: string): Observable<string[]> {
|
||||||
return this.route.queryParamMap.map((map) => map.getAll(paramName));
|
return this.route.queryParamMap.map((map) => [...map.getAll(paramName)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryParameterValue(paramName: string): Observable<string> {
|
getQueryParameterValue(paramName: string): Observable<string> {
|
||||||
@@ -25,31 +28,16 @@ export class RouteService {
|
|||||||
return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1);
|
return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
addQueryParameterValue(paramName: string, paramValue: string): Observable<Params> {
|
getQueryParamsWithPrefix(prefix: string): Observable<Params> {
|
||||||
return this.route.queryParams.map((currentParams) => {
|
return this.route.queryParamMap
|
||||||
const newParam = {};
|
.map((map) => {
|
||||||
newParam[paramName] = [...convertToParamMap(currentParams).getAll(paramName), paramValue];
|
const params = {};
|
||||||
return Object.assign({}, currentParams, newParam);
|
map.keys
|
||||||
});
|
.filter((key) => key.startsWith(prefix))
|
||||||
}
|
.forEach((key) => {
|
||||||
|
params[key] = [...map.getAll(key)];
|
||||||
removeQueryParameterValue(paramName: string, paramValue: string): Observable<Params> {
|
});
|
||||||
return this.route.queryParams.map((currentParams) => {
|
return params;
|
||||||
const newParam = {};
|
});
|
||||||
const currentFilterParams = convertToParamMap(currentParams).getAll(paramName);
|
|
||||||
if (isNotEmpty(currentFilterParams)) {
|
|
||||||
newParam[paramName] = currentFilterParams.filter((param) => (param !== paramValue));
|
|
||||||
}
|
|
||||||
return Object.assign({}, currentParams, newParam);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
removeQueryParameter(paramName: string): Observable<Params> {
|
|
||||||
return this.route.queryParams.map((currentParams) => {
|
|
||||||
const newParam = {};
|
|
||||||
newParam[paramName] = {};
|
|
||||||
return Object.assign({}, currentParams, newParam);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{
|
yarn add{
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
|
10
yarn.lock
10
yarn.lock
@@ -122,6 +122,12 @@
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@ngx-translate/http-loader/-/http-loader-2.0.1.tgz#aa67788e64bfa8652691a77b022b3b4031209113"
|
resolved "https://registry.yarnpkg.com/@ngx-translate/http-loader/-/http-loader-2.0.1.tgz#aa67788e64bfa8652691a77b022b3b4031209113"
|
||||||
|
|
||||||
|
"@types/acorn@^4.0.3":
|
||||||
|
version "4.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.3.tgz#d1f3e738dde52536f9aad3d3380d14e448820afd"
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "*"
|
||||||
|
|
||||||
"@types/body-parser@*":
|
"@types/body-parser@*":
|
||||||
version "1.16.8"
|
version "1.16.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.8.tgz#687ec34140624a3bec2b1a8ea9268478ae8f3be3"
|
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.16.8.tgz#687ec34140624a3bec2b1a8ea9268478ae8f3be3"
|
||||||
@@ -139,6 +145,10 @@
|
|||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/deep-freeze/-/deep-freeze-0.1.1.tgz#0e1ee6ceee06f51baeb663deec0bb7780bd72827"
|
resolved "https://registry.yarnpkg.com/@types/deep-freeze/-/deep-freeze-0.1.1.tgz#0e1ee6ceee06f51baeb663deec0bb7780bd72827"
|
||||||
|
|
||||||
|
"@types/estree@*":
|
||||||
|
version "0.0.38"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2"
|
||||||
|
|
||||||
"@types/events@*":
|
"@types/events@*":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
|
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
|
||||||
|
Reference in New Issue
Block a user