mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-14 05:23:06 +00:00
45621: filters facet route service
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<a *ngFor="let value of filterValues; let i=index" class="d-block" [routerLink]="[getSearchLink()]"
|
||||
[queryParams]="getQueryParams(value)">
|
||||
[queryParams]="getQueryParams(value) | async">
|
||||
<ng-template [ngIf]="i < (facetCount | async)">
|
||||
<input type="checkbox" [checked]="isChecked(value)"/>
|
||||
<input type="checkbox" [checked]="isChecked(value) | async"/>
|
||||
<span class="filter-value">{{value.value}}</span>
|
||||
<span class="filter-value-count float-right">({{value.count}})</span>
|
||||
</ng-template>
|
||||
|
@@ -30,16 +30,16 @@ export class SidebarFacetFilterComponent implements OnInit {
|
||||
this.currentPage = this.filterService.getPage(this.filterConfig.name);
|
||||
}
|
||||
|
||||
isChecked(value: FacetValue) {
|
||||
return this.filterService.isFilterActive(this.filterConfig.name, value.value);
|
||||
isChecked(value: FacetValue): Observable<boolean> {
|
||||
return this.filterService.isFilterActive(this.filterConfig.paramName, value.value);
|
||||
}
|
||||
|
||||
getSearchLink() {
|
||||
return this.filterService.searchLink;
|
||||
}
|
||||
|
||||
getQueryParams(value: FacetValue): Params {
|
||||
return this.filterService.switchFilterInURL(this.filterConfig, value.value);
|
||||
getQueryParams(value: FacetValue): Observable<Params> {
|
||||
return this.filterService.getFilterValueURL(this.filterConfig, value.value);
|
||||
}
|
||||
|
||||
get facetCount(): Observable<number> {
|
||||
|
@@ -3,6 +3,6 @@
|
||||
[ngClass]="(isCollapsed() | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
||||
<div [@slide]="(isCollapsed() | async) ? 'collapsed' : 'expanded'" class="search-filter-wrapper">
|
||||
<ds-search-facet-filter [filterConfig]="filter"
|
||||
[filterValues]="filterValues.payload | async"></ds-search-facet-filter>
|
||||
[filterValues]="(filterValues | async)?.payload"></ds-search-facet-filter>
|
||||
</div>
|
||||
</div>
|
@@ -22,7 +22,7 @@ import { slide } from '../../../shared/animations/slide';
|
||||
|
||||
export class SidebarFilterComponent implements OnInit {
|
||||
@Input() filter: SearchFilterConfig;
|
||||
filterValues: RemoteData<FacetValue[]>;
|
||||
filterValues: Observable<RemoteData<FacetValue[]>>;
|
||||
|
||||
constructor(private searchService: SearchService, private filterService: SearchFilterService) {
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
SearchFilterToggleAction
|
||||
} from './search-filter.actions';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { ActivatedRoute, convertToParamMap, Params, Router } from '@angular/router';
|
||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
@@ -21,55 +21,49 @@ import { SearchService } from '../../search-service/search.service';
|
||||
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||
|
||||
@Injectable()
|
||||
export class SearchFilterService implements OnDestroy {
|
||||
private sub;
|
||||
export class SearchFilterService {
|
||||
|
||||
constructor(private store: Store<SearchFiltersState>,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private searchService: SearchService) {
|
||||
this.route.queryParams.subscribe((params) => {
|
||||
console.log(params);
|
||||
})
|
||||
}
|
||||
|
||||
isFilterActive(filterName: string, filterValue: string): boolean {
|
||||
let filterConfig: SearchFilterConfig;
|
||||
this.sub = this.searchService.getConfig().payload
|
||||
.subscribe((configuration) => filterConfig = configuration
|
||||
.find((config: SearchFilterConfig) => config.name === filterName));
|
||||
return isNotEmpty(this.route.snapshot.queryParams[filterConfig.paramName]) && [...this.route.snapshot.queryParams[filterConfig.paramName]].indexOf(filterValue, 0) > -1;
|
||||
isFilterActive(paramName: string, filterValue: string): Observable<boolean> {
|
||||
return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(filterValue) > -1 );
|
||||
}
|
||||
|
||||
switchFilterInURL(filterConfig: SearchFilterConfig, value: string) {
|
||||
console.log(this.route.snapshot.queryParams);
|
||||
if (this.isFilterActive(filterConfig.name, value)) {
|
||||
getFilterValueURL(filterConfig: SearchFilterConfig, value: string): Observable<Params> {
|
||||
return this.isFilterActive(filterConfig.paramName, value).flatMap((isActive) => {
|
||||
if (isActive) {
|
||||
return this.removeQueryParameter(filterConfig.paramName, value);
|
||||
} else {
|
||||
return this.addQueryParameter(filterConfig.paramName, value);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addQueryParameter(paramName: string, value: string): Params {
|
||||
const currentParams = this.route.snapshot.queryParams;
|
||||
addQueryParameter(paramName: string, value: string): Observable<Params> {
|
||||
return this.route.queryParams.map((currentParams) => {
|
||||
const newParam = {};
|
||||
if ((currentParams[paramName])) {
|
||||
newParam[paramName] = [...currentParams[paramName], value];
|
||||
} else {
|
||||
newParam[paramName] = [value];
|
||||
}
|
||||
newParam[paramName] = [...convertToParamMap(currentParams).getAll(paramName), value];
|
||||
return Object.assign({}, currentParams, newParam);
|
||||
});
|
||||
}
|
||||
|
||||
removeQueryParameter(paramName: string, value: string): Params {
|
||||
const currentParams = this.route.snapshot.queryParams;
|
||||
removeQueryParameter(paramName: string, value: string): Observable<Params> {
|
||||
return this.route.queryParams.map((currentParams) => {
|
||||
const newParam = {};
|
||||
let currentFilterParams = [...currentParams[paramName]];
|
||||
const currentFilterParams = convertToParamMap(currentParams).getAll(paramName);
|
||||
if (isNotEmpty(currentFilterParams)) {
|
||||
const index = currentFilterParams.indexOf(value, 0);
|
||||
if (index > -1) {
|
||||
currentFilterParams = currentFilterParams.splice(index, 1);
|
||||
}
|
||||
newParam[paramName] = currentFilterParams;
|
||||
newParam[paramName] = currentFilterParams.filter((param) => (param !== value));
|
||||
}
|
||||
console.log(Object.assign({}, currentParams, newParam));
|
||||
return Object.assign({}, currentParams, newParam);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
get searchLink() {
|
||||
@@ -113,12 +107,6 @@ export class SearchFilterService implements OnDestroy {
|
||||
public increasePage(filterName: string): void {
|
||||
this.store.dispatch(new SearchFilterIncreasePageAction(filterName));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.sub !== undefined) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function filterByNameSelector(name: string): MemoizedSelector<SearchFiltersState, SearchFilterState> {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<h2>{{"search.filters.head" | translate}}</h2>
|
||||
<div *ngIf="filters.hasSucceeded | async">
|
||||
<div *ngFor="let filter of (filters.payload | async)">
|
||||
<div *ngIf="(filters | async).hasSucceeded">
|
||||
<div *ngFor="let filter of (filters | async).payload">
|
||||
<ds-search-filter [filter]="filter"></ds-search-filter>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core';
|
||||
import { SearchService } from '../search-service/search.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -16,7 +17,7 @@ import { SearchFilterConfig } from '../search-service/search-filter-config.model
|
||||
})
|
||||
|
||||
export class SidebarFiltersComponent {
|
||||
filters: RemoteData<SearchFilterConfig[]>;
|
||||
filters: Observable<RemoteData<SearchFilterConfig[]>>;
|
||||
constructor(private searchService: SearchService) {
|
||||
this.filters = searchService.getConfig();
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="search-page row">
|
||||
<ds-search-sidebar *ngIf="!(isMobileView | async)" class="col-3 sidebar-sm-sticky"
|
||||
id="search-sidebar"
|
||||
resultCount="{{(results.pageInfo | async)?.totalElements}}"></ds-search-sidebar>
|
||||
resultCount="{{(results | async)?.pageInfo.totalElements}}"></ds-search-sidebar>
|
||||
<div class="col-12 col-sm-9">
|
||||
<ds-search-form id="search-form"
|
||||
[query]="query"
|
||||
@@ -16,9 +16,14 @@
|
||||
[@pushInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
||||
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12"
|
||||
id="search-sidebar-xs"
|
||||
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
||||
resultCount="{{(results | async)?.pageInfo.totalElements}}"
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"></ds-search-sidebar>
|
||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}">
|
||||
</ds-search-sidebar>
|
||||
|
||||
<ng-template [ngIf]="(isSidebarCollapsed() | async)">Not Active - Sidebar is collapsed</ng-template>
|
||||
<ng-template [ngIf]="!(isSidebarCollapsed() | async)">Active- Sidebar is not collapsed</ng-template>
|
||||
|
||||
<div id="search-content" class="col-12">
|
||||
<div class="d-block d-sm-none search-controls clearfix">
|
||||
<ds-view-mode-switch></ds-view-mode-switch>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
@@ -18,7 +18,7 @@ import { By } from '@angular/platform-browser';
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||
|
||||
describe('SearchPageComponent', () => {
|
||||
fdescribe('SearchPageComponent', () => {
|
||||
let comp: SearchPageComponent;
|
||||
let fixture: ComponentFixture<SearchPageComponent>;
|
||||
let searchServiceObject: SearchService;
|
||||
@@ -41,10 +41,11 @@ describe('SearchPageComponent', () => {
|
||||
})
|
||||
};
|
||||
const sidebarService = {
|
||||
isCollapsed: Observable.of(true),
|
||||
collapse: () => this.isCollapsed = Observable.of(true),
|
||||
expand: () => this.isCollapsed = Observable.of(false)
|
||||
}
|
||||
collapsed: Observable.of(true),
|
||||
collapse: () => {this.collapsed = Observable.of(true)},
|
||||
expand: () => {console.log('expand'); this.collapsed = Observable.of(false)},
|
||||
isCollapsed: () => {if (this.collapse) {this.collapsed.subscribe(a => console.log(a))};return this.collapsed},
|
||||
};
|
||||
|
||||
const mockCommunityList = [];
|
||||
const communityDataServiceStub = {
|
||||
@@ -178,11 +179,12 @@ describe('SearchPageComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
menu = fixture.debugElement.query(By.css('#search-sidebar-xs')).nativeElement;
|
||||
comp.isSidebarCollapsed = () => Observable.of(true);
|
||||
fixture.detectChanges();
|
||||
sidebarService.collapse();
|
||||
});
|
||||
|
||||
it('should close the sidebar', () => {
|
||||
sidebarService.isCollapsed().subscribe((v) => console.log('after closing, is collapsed is...: ', v));
|
||||
console.log(menu.classList);
|
||||
expect(menu.classList).not.toContain('active');
|
||||
});
|
||||
|
||||
@@ -193,12 +195,13 @@ describe('SearchPageComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
menu = fixture.debugElement.query(By.css('#search-sidebar-xs')).nativeElement;
|
||||
comp.isSidebarCollapsed = () => Observable.of(false);
|
||||
fixture.detectChanges();
|
||||
sidebarService.expand();
|
||||
});
|
||||
|
||||
it('should open the menu', () => {
|
||||
sidebarService.isCollapsed.subscribe((a) => {console.log(a)})
|
||||
sidebarService.isCollapsed().subscribe((v) => console.log('after opening, is collapsed is...: ', v));
|
||||
console.log(menu.classList);
|
||||
debugger;
|
||||
expect(menu.classList).toContain('active');
|
||||
});
|
||||
|
||||
|
@@ -107,6 +107,6 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public isSidebarCollapsed(): Observable<boolean> {
|
||||
return this.sidebarService.isCollapsed;
|
||||
return this.sidebarService.isCollapsed();
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,8 @@ export class SearchSidebarService {
|
||||
this.isCollapsdeInStored = this.store.select(sidebarCollapsedSelector);
|
||||
}
|
||||
|
||||
get isCollapsed(): Observable<boolean> {
|
||||
isCollapsed(): Observable<boolean> {
|
||||
console.log('NEEN');
|
||||
return Observable.combineLatest(
|
||||
this.isMobileView,
|
||||
this.isCollapsdeInStored,
|
||||
|
@@ -46,7 +46,7 @@ export function getMetaReducers(config: GlobalConfig): Array<MetaReducer<AppStat
|
||||
const DEV_MODULES: any[] = [];
|
||||
|
||||
if (!ENV_CONFIG.production) {
|
||||
DEV_MODULES.push(StoreDevtoolsModule.instrument({ maxAge: 50 }));
|
||||
DEV_MODULES.push(StoreDevtoolsModule.instrument({ maxAge: 500 }));
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
|
51
src/app/shared/route.service.spec.ts
Normal file
51
src/app/shared/route.service.spec.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { RouteService } from './route.service';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
describe('RouteService', () => {
|
||||
let service: RouteService;
|
||||
const paramName1 = 'name';
|
||||
const paramValue1 = 'Test Name';
|
||||
const paramName2 = 'id';
|
||||
const paramValue2a = 'Test id';
|
||||
const paramValue2b = 'another id';
|
||||
const nonExistingParamName = 'non existing name';
|
||||
const nonExistingParamValue = 'non existing value';
|
||||
|
||||
const paramObject: Params = {};
|
||||
|
||||
paramObject[paramName1] = paramValue1;
|
||||
paramObject[paramName2] = [paramValue2a, paramValue2b];
|
||||
|
||||
beforeEach(async(() => {
|
||||
return TestBed.configureTestingModule({
|
||||
declarations: [RouteService],
|
||||
providers: [
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
params: Observable.of([paramObject]),
|
||||
},
|
||||
},
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = new RouteService(TestBed.get(ActivatedRoute));
|
||||
});
|
||||
|
||||
describe('hasQueryParam', () => {
|
||||
it(' should return true when the parameter name exists', () => {
|
||||
service.hasQueryParam(paramName1).subscribe((status) => {
|
||||
expect(status).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it(' should return false when the parameter name does not exists', () => {
|
||||
service.hasQueryParam(nonExistingParamName).subscribe((status) => {
|
||||
expect(status).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
47
src/app/shared/route.service.ts
Normal file
47
src/app/shared/route.service.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, convertToParamMap, Params, } from '@angular/router';
|
||||
import { isNotEmpty } from './empty.util';
|
||||
|
||||
@Injectable()
|
||||
export class RouteService {
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
hasQueryParam(paramName: string): Observable<boolean> {
|
||||
return this.route.queryParamMap.map((map) => map.has(paramName));
|
||||
}
|
||||
|
||||
hasQueryParamWithValue(paramName: string, paramValue: string): Observable<boolean> {
|
||||
return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1);
|
||||
}
|
||||
|
||||
addQueryParameterValue(paramName: string, paramValue: string): Observable<Params> {
|
||||
return this.route.queryParams.map((currentParams) => {
|
||||
const newParam = {};
|
||||
newParam[paramName] = [...convertToParamMap(currentParams).getAll(paramName), paramValue];
|
||||
return Object.assign({}, currentParams, newParam);
|
||||
});
|
||||
}
|
||||
|
||||
removeQueryParameterValue(paramName: string, paramValue: string): Observable<Params> {
|
||||
return this.route.queryParams.map((currentParams) => {
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user