mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
45621: filter components for sidebar
This commit is contained in:
@@ -93,7 +93,19 @@
|
||||
},
|
||||
"filters": {
|
||||
"head": "Filters",
|
||||
"reset": "Reset filters"
|
||||
"reset": "Reset filters",
|
||||
"facet-filter": {
|
||||
"show-more": "Show more",
|
||||
"author": {
|
||||
"placeholder": "Author name"
|
||||
},
|
||||
"scope": {
|
||||
"placeholder": "Scope filter"
|
||||
},
|
||||
"subject": {
|
||||
"placeholder": "Subject"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"loading": {
|
||||
|
@@ -0,0 +1,8 @@
|
||||
<a *ngFor="let value of filterValues" class="d-block" [routerLink]="[getSearchLink()]" [queryParams]="getQueryParams(value) | async">
|
||||
<input type="checkbox" [checked]="isChecked(value)"/>
|
||||
<span class="filter-value">{{value.value}}</span>
|
||||
<span class="filter-value-count float-right">({{value.count}})</span>
|
||||
</a>
|
||||
<a href="">{{"search.filters.facet-filter.show-more" | translate}}</a>
|
||||
|
||||
<input type="text" [placeholder]="'search.filters.facet-filter.' + filterConfig.name + '.placeholder'| translate"/>
|
@@ -0,0 +1,2 @@
|
||||
@import '../../../../../styles/variables.scss';
|
||||
@import '../../../../../styles/mixins.scss';
|
@@ -0,0 +1,41 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FacetValue } from '../../../search-service/facet-value.model';
|
||||
import { SearchFilterConfig } from '../../../search-service/search-filter-config.model';
|
||||
import { SearchService } from '../../../search-service/search.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-facet-filter',
|
||||
styleUrls: ['./search-facet-filter.component.scss'],
|
||||
templateUrl: './search-facet-filter.component.html',
|
||||
})
|
||||
|
||||
export class SidebarFacetFilterComponent {
|
||||
@Input() filterValues: FacetValue[];
|
||||
@Input() filterConfig: SearchFilterConfig;
|
||||
|
||||
constructor(private searchService: SearchService, private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
isChecked(value: FacetValue) {
|
||||
return this.searchService.isFilterActive(this.filterConfig.name, value.value);
|
||||
}
|
||||
|
||||
getSearchLink() {
|
||||
return this.searchService.getSearchLink();
|
||||
}
|
||||
|
||||
getQueryParams(value: FacetValue): Observable<any> {
|
||||
const params = {};
|
||||
params[this.filterConfig.paramName] = value.value;
|
||||
return this.route.queryParams.map((p) => Object.assign({}, p, params))
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<div (click)="toggle()" class="filter-name">{{filter.name}} <span class="fa float-right" [ngClass]="isCollapsed ? 'fa-plus' : 'fa-minus'"></span></div>
|
||||
<ds-search-facet-filter [filterConfig]="filter" [filterValues]="filterValues.payload | async"></ds-search-facet-filter>
|
||||
</div>
|
@@ -0,0 +1,2 @@
|
||||
@import '../../../../styles/variables.scss';
|
||||
@import '../../../../styles/mixins.scss';
|
@@ -0,0 +1,34 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
|
||||
import { SearchService } from '../../search-service/search.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { FacetValue } from '../../search-service/facet-value.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
* The route parameter 'id' is used to request the item it represents.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-filter',
|
||||
styleUrls: ['./search-filter.component.scss'],
|
||||
templateUrl: './search-filter.component.html',
|
||||
})
|
||||
|
||||
export class SidebarFilterComponent implements OnInit {
|
||||
@Input() filter: SearchFilterConfig;
|
||||
filterValues: RemoteData<FacetValue[]>;
|
||||
isCollapsed = false;
|
||||
|
||||
constructor(private searchService: SearchService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filterValues = this.searchService.getFacetValuesFor(this.filter.name);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<h2>{{"search.filters.head" | translate}}</h2>
|
||||
<div>
|
||||
<div *ngFor="let filter of filters">
|
||||
|
||||
<div *ngIf="filters.hasSucceeded | async">
|
||||
<div *ngFor="let filter of (filters.payload | async)">
|
||||
<ds-search-filter [filter]="filter"></ds-search-filter>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-primary" [href]="getClearFiltersLink() | async" role="button">{{"search.filters.reset" | translate}}</a>
|
||||
<a class="btn btn-primary" [routerLink]="[getSearchLink()]" [queryParams]="getClearFiltersQueryParams()" role="button">{{"search.filters.reset" | translate}}</a>
|
@@ -1,6 +1,7 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { SearchService } from '../search-service/search.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { SearchFilterConfig } from '../search-service/search-filter-config.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -15,10 +16,16 @@ import { Observable } from 'rxjs/Observable';
|
||||
})
|
||||
|
||||
export class SidebarFiltersComponent {
|
||||
@Input() filters;
|
||||
constructor(private searchService: SearchService) {}
|
||||
filters: RemoteData<SearchFilterConfig[]>;
|
||||
constructor(private searchService: SearchService) {
|
||||
this.filters = searchService.getConfig();
|
||||
}
|
||||
|
||||
getClearFiltersLink(): Observable<string> {
|
||||
return this.searchService.getClearFiltersLink();
|
||||
getClearFiltersQueryParams(): any {
|
||||
return this.searchService.getClearFiltersQueryParams();
|
||||
}
|
||||
|
||||
getSearchLink() {
|
||||
return this.searchService.getSearchLink();
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,26 @@
|
||||
<div class="container">
|
||||
<div class="search-page">
|
||||
<ds-search-sidebar dsStick *ngIf="!(isMobileView | async)" class="col-3 sidebar-sm-fixed"
|
||||
<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>
|
||||
<div id="search-header" class="row">
|
||||
<ds-search-form id="search-form" class="col-12 col-sm-9 ml-sm-auto"
|
||||
<div class="col-12 col-sm-9">
|
||||
<ds-search-form id="search-form"
|
||||
[query]="query"
|
||||
[scope]="scopeObject?.payload | async"
|
||||
[currentParams]="currentParams"
|
||||
[scopes]="scopeList?.payload">
|
||||
</ds-search-form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="search-body"
|
||||
class="row-offcanvas row-offcanvas-left"
|
||||
[@slideInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
|
||||
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12" id="search-sidebar-xs"
|
||||
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12"
|
||||
id="search-sidebar-xs"
|
||||
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
||||
(toggleSidebar)="closeSidebar()" [ngClass]="{'active': !(isSidebarCollapsed() | async)}"></ds-search-sidebar>
|
||||
<div id="search-content" class="col-12 col-sm-9 ml-sm-auto">
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"></ds-search-sidebar>
|
||||
<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>
|
||||
<button (click)="openSidebar()" aria-controls="#search-body"
|
||||
class="btn btn-outline-primary float-right open-sidebar"><i
|
||||
@@ -34,5 +34,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -5,12 +5,6 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#search-content, #search-form {
|
||||
display: block;
|
||||
@include media-breakpoint-down(xs) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .search-controls {
|
||||
margin-bottom: $spacer;
|
||||
@@ -43,15 +37,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-sm-fixed {
|
||||
.sidebar-sm-sticky{
|
||||
@include media-breakpoint-up(sm) {
|
||||
position: absolute;
|
||||
margin-top: -$content-spacing;
|
||||
padding-top: $content-spacing;
|
||||
&.stick {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
margin-top: 0px;
|
||||
position: fixed;
|
||||
}
|
||||
z-index: $zindex-sticky;
|
||||
padding-top: $content-spacing;
|
||||
margin-top: -$content-spacing;
|
||||
align-self: flex-start;
|
||||
display: block;
|
||||
}
|
||||
}
|
@@ -13,6 +13,8 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||
import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { SidebarFiltersComponent } from './search-filters/search-filters.component';
|
||||
import { SidebarFilterComponent } from './search-filters/search-filter/search-filter.component';
|
||||
import { SidebarFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
||||
|
||||
const effects = [
|
||||
SearchSidebarEffects
|
||||
@@ -32,7 +34,9 @@ const effects = [
|
||||
ItemSearchResultListElementComponent,
|
||||
CollectionSearchResultListElementComponent,
|
||||
CommunitySearchResultListElementComponent,
|
||||
SidebarFiltersComponent
|
||||
SidebarFiltersComponent,
|
||||
SidebarFilterComponent,
|
||||
SidebarFacetFilterComponent
|
||||
],
|
||||
providers: [
|
||||
SearchService,
|
||||
|
@@ -3,7 +3,6 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { SearchService } from './search.service';
|
||||
import { ItemDataService } from './../../core/data/item-data.service';
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SearchResult } from '../search-result.model';
|
||||
@@ -31,7 +31,7 @@ function shuffle(array: any[]) {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
export class SearchService implements OnDestroy {
|
||||
|
||||
totalPages = 5;
|
||||
mockedHighlights: string[] = new Array(
|
||||
@@ -46,6 +46,8 @@ export class SearchService {
|
||||
'<em>This was blank in the actual item, no abstract</em>',
|
||||
'<em>The QSAR DataBank (QsarDB) repository</em>',
|
||||
);
|
||||
private sub;
|
||||
searchLink = '/search';
|
||||
|
||||
config: SearchFilterConfig[] = [
|
||||
Object.assign(new SearchFilterConfig(),
|
||||
@@ -170,13 +172,16 @@ export class SearchService {
|
||||
}
|
||||
|
||||
getFacetValuesFor(searchFilterConfigName: string): RemoteData<FacetValue[]> {
|
||||
|
||||
const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === searchFilterConfigName);
|
||||
|
||||
const values: FacetValue[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const value = searchFilterConfigName + ' ' + (i + 1);
|
||||
values.push({
|
||||
value: value,
|
||||
count: Math.floor(Math.random() * 20) + 20 * (5 - i), // make sure first results have the highest (random) count
|
||||
search: 'https://dspace7.4science.it/dspace-spring-rest/api/search?f.' + searchFilterConfigName + '=' + encodeURI(value)
|
||||
search: decodeURI(this.router.url) + (this.router.url.includes('?') ? '&' : '?') + filterConfig.paramName + '=' + value
|
||||
});
|
||||
}
|
||||
const requestPending = Observable.of(false);
|
||||
@@ -213,17 +218,36 @@ export class SearchService {
|
||||
queryParamsHandling: 'merge'
|
||||
};
|
||||
|
||||
this.router.navigate(['/search'], navigationExtras);
|
||||
this.router.navigate([this.searchLink], navigationExtras);
|
||||
}
|
||||
|
||||
getClearFiltersLink(): Observable<string> {
|
||||
const url = '/search?';
|
||||
return this.route.queryParamMap
|
||||
.map((map) => { return url.concat(map.keys
|
||||
getClearFiltersQueryParams(): any {
|
||||
const params = {};
|
||||
this.sub = this.route.queryParamMap
|
||||
.subscribe((map) => {
|
||||
map.keys
|
||||
.filter((key) => this.config
|
||||
.findIndex((conf: SearchFilterConfig) => conf.paramName === key) < 0)
|
||||
.map((key) => { return key + '=' + map.get(key) })
|
||||
.join('&'))})
|
||||
.first();
|
||||
.forEach((key) => {
|
||||
params[key] = map.get(key);
|
||||
})
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
isFilterActive(filterName: string, filterValue: string): boolean {
|
||||
const filterConfig = this.config.find((config: SearchFilterConfig) => config.name === filterName);
|
||||
return isNotEmpty(this.router.url.match(filterConfig.paramName + '=' + encodeURI(filterValue) + '(&(.*))?$'));
|
||||
}
|
||||
|
||||
getSearchLink() {
|
||||
return this.searchLink;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.sub !== undefined) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row">
|
||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/search">
|
||||
<div *ngIf="isNotEmpty(scopes | async)" class="col-12 col-sm-3">
|
||||
<select [(ngModel)]="selectedId" name="scope" class="form-control" aria-label="Search scope" [compareWith]="byId">
|
||||
<option value>{{'search.form.search_dspace' | translate}}</option>
|
||||
|
@@ -30,7 +30,6 @@ import { SearchResultListElementComponent } from '../object-list/search-result-l
|
||||
import { SearchFormComponent } from './search-form/search-form.component';
|
||||
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
|
||||
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
||||
import { ScrollAndStickDirective } from './utils/scroll-and-stick.directive';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -68,7 +67,6 @@ const COMPONENTS = [
|
||||
];
|
||||
|
||||
const DIRECTIVES = [
|
||||
ScrollAndStickDirective,
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
|
@@ -1,42 +0,0 @@
|
||||
import { NativeWindowRef, NativeWindowService } from '../window.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AfterViewInit, Directive, ElementRef, Inject, PLATFORM_ID } from '@angular/core';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[dsStick]'
|
||||
})
|
||||
export class ScrollAndStickDirective implements AfterViewInit {
|
||||
|
||||
private initialY: number;
|
||||
|
||||
constructor(private _element: ElementRef, @Inject(NativeWindowService) private _window: NativeWindowRef, @Inject(PLATFORM_ID) private platformId) {
|
||||
if (isPlatformBrowser(platformId)) {
|
||||
this.subscribeForScrollEvent();
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.initialY = this._element.nativeElement.getBoundingClientRect().top;
|
||||
}
|
||||
|
||||
subscribeForScrollEvent() {
|
||||
|
||||
const obs = Observable.fromEvent(window, 'scroll');
|
||||
|
||||
obs.subscribe((e) => this.handleScrollEvent(e));
|
||||
}
|
||||
|
||||
handleScrollEvent(e) {
|
||||
|
||||
if (this._window.nativeWindow.pageYOffset >= this.initialY) {
|
||||
|
||||
this._element.nativeElement.classList.add('stick');
|
||||
|
||||
} else {
|
||||
|
||||
this._element.nativeElement.classList.remove('stick');
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user