diff --git a/resources/i18n/en.json b/resources/i18n/en.json index a1af4bcc6a..c803cda073 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -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": { diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html new file mode 100644 index 0000000000..7d1f7f2e89 --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.html @@ -0,0 +1,8 @@ + + + {{value.value}} + ({{value.count}}) + +{{"search.filters.facet-filter.show-more" | translate}} + + \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.scss b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.scss new file mode 100644 index 0000000000..9ad844019b --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.scss @@ -0,0 +1,2 @@ +@import '../../../../../styles/variables.scss'; +@import '../../../../../styles/mixins.scss'; \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts new file mode 100644 index 0000000000..2565c11945 --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -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 { + const params = {}; + params[this.filterConfig.paramName] = value.value; + return this.route.queryParams.map((p) => Object.assign({}, p, params)) + } + +} diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-filter.component.html new file mode 100644 index 0000000000..2279564359 --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.html @@ -0,0 +1,4 @@ +
+
{{filter.name}}
+ +
\ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.scss b/src/app/+search-page/search-filters/search-filter/search-filter.component.scss new file mode 100644 index 0000000000..3e77826dc2 --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.scss @@ -0,0 +1,2 @@ +@import '../../../../styles/variables.scss'; +@import '../../../../styles/mixins.scss'; \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts new file mode 100644 index 0000000000..c26efd41ad --- /dev/null +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -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; + isCollapsed = false; + + constructor(private searchService: SearchService) { + } + + ngOnInit() { + this.filterValues = this.searchService.getFacetValuesFor(this.filter.name); + } + + toggle() { + this.isCollapsed = !this.isCollapsed; + } +} \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filters.component.html b/src/app/+search-page/search-filters/search-filters.component.html index f42ad620ed..270649598f 100644 --- a/src/app/+search-page/search-filters/search-filters.component.html +++ b/src/app/+search-page/search-filters/search-filters.component.html @@ -1,7 +1,7 @@

{{"search.filters.head" | translate}}

-
-
- +
+
+
-{{"search.filters.reset" | translate}} \ No newline at end of file +{{"search.filters.reset" | translate}} \ No newline at end of file diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index b1b8357d9f..216e8c0832 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -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; + constructor(private searchService: SearchService) { + this.filters = searchService.getConfig(); + } - getClearFiltersLink(): Observable { - return this.searchService.getClearFiltersLink(); + getClearFiltersQueryParams(): any { + return this.searchService.getClearFiltersQueryParams(); + } + + getSearchLink() { + return this.searchService.getSearchLink(); } } diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index a6c9834e37..25bd741415 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -1,35 +1,36 @@
-
- + -
- + -
-
-
- -
-
- - - +
+
+ +
+
+ + +
+
-
diff --git a/src/app/+search-page/search-page.component.scss b/src/app/+search-page/search-page.component.scss index b670549141..2ca44644f8 100644 --- a/src/app/+search-page/search-page.component.scss +++ b/src/app/+search-page/search-page.component.scss @@ -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; + position: sticky; + position: -webkit-sticky; + top: 0; + z-index: $zindex-sticky; padding-top: $content-spacing; - &.stick { - top: 0; - margin-top: 0px; - position: fixed; - } + margin-top: -$content-spacing; + align-self: flex-start; + display: block; } } \ No newline at end of file diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index be08e0c307..58e590d4dc 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -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, diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts index 489ac76763..f2564eed27 100644 --- a/src/app/+search-page/search-service/search.service.spec.ts +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -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'; diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index be82f3e2ee..9d65eb6c0e 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -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 { 'This was blank in the actual item, no abstract', 'The QSAR DataBank (QsarDB) repository', ); + private sub; + searchLink = '/search'; config: SearchFilterConfig[] = [ Object.assign(new SearchFilterConfig(), @@ -170,13 +172,16 @@ export class SearchService { } getFacetValuesFor(searchFilterConfigName: string): RemoteData { + + 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 { - const url = '/search?'; - return this.route.queryParamMap - .map((map) => { return url.concat(map.keys - .filter((key) => this.config - .findIndex((conf: SearchFilterConfig) => conf.paramName === key) < 0) - .map((key) => { return key + '=' + map.get(key) }) - .join('&'))}) - .first(); + getClearFiltersQueryParams(): any { + const params = {}; + this.sub = this.route.queryParamMap + .subscribe((map) => { + map.keys + .filter((key) => this.config + .findIndex((conf: SearchFilterConfig) => conf.paramName === key) < 0) + .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(); + } + } + } diff --git a/src/app/shared/search-form/search-form.component.html b/src/app/shared/search-form/search-form.component.html index dfdc20024a..76b234ec0c 100644 --- a/src/app/shared/search-form/search-form.component.html +++ b/src/app/shared/search-form/search-form.component.html @@ -1,4 +1,4 @@ -
+