From cdcacedfaecb911530a84477ce7a010864e7e250 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 12 Feb 2019 10:40:47 +0100 Subject: [PATCH] 59695: Starts-with implementation for browse-by date --- config/environment.default.js | 11 ++- resources/i18n/en.json | 5 ++ .../browse-by-date-page.component.ts | 79 ++++++++++++++----- .../+browse-by/browse-by-routing.module.ts | 2 +- src/app/core/browse/browse.service.ts | 24 +++++- ...rowse-by-starts-with-abstract.component.ts | 56 ++++++++++++- .../browse-by-starts-with-date.component.html | 26 +++++- .../browse-by-starts-with-date.component.scss | 7 ++ .../browse-by-starts-with-text.component.html | 5 +- src/config/browse-by-config.interface.ts | 7 ++ src/config/global-config.interface.ts | 2 + 11 files changed, 190 insertions(+), 34 deletions(-) create mode 100644 src/config/browse-by-config.interface.ts diff --git a/config/environment.default.js b/config/environment.default.js index d3758e66bd..b99f027c08 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -72,5 +72,14 @@ module.exports = { code: 'nl', label: 'Nederlands', active: false, - }] + }], + // Browse-By Pages + browseBy: { + // Amount of years to display using jumps of one year (current year - oneYearLimit) + oneYearLimit: 10, + // Limit for years to display using jumps of five years (current year - fiveYearLimit) + fiveYearLimit: 30, + // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) + defaultLowerLimit: 1900 + } }; diff --git a/resources/i18n/en.json b/resources/i18n/en.json index b4a421baf4..a357a9c622 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -281,6 +281,11 @@ }, "browse": { "title": "Browsing {{ collection }} by {{ field }} {{ value }}", + "startsWith": { + "choose_year": "(Choose year)", + "type_year": "Or type in a year:", + "submit": "Go" + }, "metadata": { "title": "Title", "author": "Author", diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 0d07bcc073..9ae8ea56df 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { BrowseByMetadataPageComponent, browseParamsToOptions @@ -6,6 +6,14 @@ import { import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { BrowseByStartsWithType } from '../../shared/browse-by/browse-by.component'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { Item } from '../../core/shared/item.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BrowseService } from '../../core/browse/browse.service'; +import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @Component({ selector: 'ds-browse-by-date-page', @@ -19,8 +27,15 @@ import { BrowseByStartsWithType } from '../../shared/browse-by/browse-by.compone */ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { - oneYearLimit = 10; - fiveYearLimit = 30; + defaultMetadataField = 'dc.date.issued'; + + public constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig, + protected route: ActivatedRoute, + protected browseService: BrowseService, + protected dsoService: DSpaceObjectDataService, + protected router: Router) { + super(route, browseService, dsoService, router); + } ngOnInit(): void { this.startsWithType = BrowseByStartsWithType.date; @@ -34,29 +49,53 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { return Object.assign({}, params, queryParams, data); }) .subscribe((params) => { + const metadataField = params.metadataField || this.defaultMetadataField; this.metadata = params.metadata || this.defaultMetadata; this.startsWith = +params.startsWith || params.startsWith; const searchOptions = browseParamsToOptions(params, Object.assign({}), this.sortConfig, this.metadata); this.updatePageWithItems(searchOptions, this.value); this.updateParent(params.scope); + this.updateStartsWithOptions(this.metadata, metadataField, params.scope); })); - const options = []; - const currentYear = new Date().getFullYear(); - const oneYearBreak = Math.floor((currentYear - this.oneYearLimit) / 5) * 5; - const fiveYearBreak = Math.floor((currentYear - this.fiveYearLimit) / 10) * 10; - const lowerLimit = 1900; // Hardcoded atm, this should be the lowest date issued - let i = currentYear; - while (i > lowerLimit) { - options.push(i); - if (i <= fiveYearBreak) { - i -= 10; - } else if (i <= oneYearBreak) { - i -= 5; - } else { - i--; - } - } - console.log(options); + } + + updateStartsWithOptions(definition: string, metadataField: string, scope?: string) { + this.subs.push( + this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData>) => { + let lowerLimit = this.config.browseBy.defaultLowerLimit; + if (firstItemRD.payload.page.length > 0) { + const date = firstItemRD.payload.page[0].findMetadata(metadataField); + if (hasValue(date) && hasValue(+date.split('-')[0])) { + lowerLimit = +date.split('-')[0]; + } + } + const options = []; + const currentYear = new Date().getFullYear(); + const oneYearBreak = Math.floor((currentYear - this.config.browseBy.oneYearLimit) / 5) * 5; + const fiveYearBreak = Math.floor((currentYear - this.config.browseBy.fiveYearLimit) / 10) * 10; + if (lowerLimit <= fiveYearBreak) { + lowerLimit -= 10; + } else if (lowerLimit <= oneYearBreak) { + lowerLimit -= 5; + } else { + lowerLimit -= 1; + } + let i = currentYear; + while (i > lowerLimit) { + options.push(i); + if (i <= fiveYearBreak) { + i -= 10; + } else if (i <= oneYearBreak) { + i -= 5; + } else { + i--; + } + } + if (isNotEmpty(options)) { + this.startsWithOptions = options; + } + }) + ); } } diff --git a/src/app/+browse-by/browse-by-routing.module.ts b/src/app/+browse-by/browse-by-routing.module.ts index 9295209d11..f4c8b688f6 100644 --- a/src/app/+browse-by/browse-by-routing.module.ts +++ b/src/app/+browse-by/browse-by-routing.module.ts @@ -8,7 +8,7 @@ import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date imports: [ RouterModule.forChild([ { path: 'title', component: BrowseByTitlePageComponent }, - { path: 'dateissued', component: BrowseByDatePageComponent, data: { metadata: 'dateissued' } }, + { path: 'dateissued', component: BrowseByDatePageComponent, data: { metadata: 'dateissued', metadataField: 'dc.date.issued' } }, { path: ':metadata', component: BrowseByMetadataPageComponent } ]) ] diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index 94c13c7d05..5388721218 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators'; import { - ensureArrayHasValue, + ensureArrayHasValue, hasValue, hasValueOperator, isEmpty, isNotEmpty, @@ -162,6 +162,28 @@ export class BrowseService { ); } + getFirstItemFor(definition: string, scope?: string): Observable>> { + return this.getBrowseDefinitions().pipe( + getBrowseDefinitionLinks(definition), + hasValueOperator(), + map((_links: any) => _links.items), + hasValueOperator(), + map((href: string) => { + const args = []; + if (hasValue(scope)) { + args.push(`scope=${scope}`); + } + args.push('page=0'); + args.push('size=1'); + if (isNotEmpty(args)) { + href = new URLCombiner(href, `?${args.join('&')}`).toString(); + } + return href; + }), + getBrowseItemsFor(this.requestService, this.responseCache, this.rdb) + ); + } + getPrevBrowseItems(items: RemoteData>): Observable>> { return observableOf(items.payload.prev).pipe( getBrowseItemsFor(this.requestService, this.responseCache, this.rdb) diff --git a/src/app/shared/browse-by/browse-by-starts-with/browse-by-starts-with-abstract.component.ts b/src/app/shared/browse-by/browse-by-starts-with/browse-by-starts-with-abstract.component.ts index c8651dba21..ff113f1773 100644 --- a/src/app/shared/browse-by/browse-by-starts-with/browse-by-starts-with-abstract.component.ts +++ b/src/app/shared/browse-by/browse-by-starts-with/browse-by-starts-with-abstract.component.ts @@ -1,6 +1,56 @@ -import { Inject } from '@angular/core'; +import { Inject, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { hasValue } from '../../empty.util'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { FormControl, FormGroup } from '@angular/forms'; -export class BrowseByStartsWithAbstractComponent { - public constructor(@Inject('startsWithOptions') public startsWithOptions: any[]) { +export class BrowseByStartsWithAbstractComponent implements OnInit, OnDestroy { + startsWith: string; + + formData: FormGroup; + + /** + * List of subscriptions + */ + subs: Subscription[] = []; + + public constructor(@Inject('startsWithOptions') public startsWithOptions: any[], + protected route: ActivatedRoute, + protected router: Router) { + } + + ngOnInit(): void { + this.subs.push( + this.route.queryParams.subscribe((params) => { + this.startsWith = params.startsWith; + }) + ); + this.formData = new FormGroup({ + startsWith: new FormControl() + }); + } + + setStartsWith(event: Event) { + this.startsWith = (event.target as HTMLInputElement).value; + this.setStartsWithParam(); + } + + setStartsWithParam() { + if (this.startsWith === '-1') { + this.startsWith = undefined; + } + this.router.navigate([], { + queryParams: Object.assign({ startsWith: this.startsWith }), + queryParamsHandling: 'merge' + }); + } + + submitForm(data) { + this.startsWith = data.startsWith; + this.setStartsWithParam(); + } + + ngOnDestroy(): void { + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.html b/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.html index c2221ed257..768d6c4049 100644 --- a/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.html +++ b/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.html @@ -1,4 +1,22 @@ -

Test Starts-With Date

-

- {{element}}, -

+
+
+
+ +
+ + + + +
+
+
+
diff --git a/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.scss b/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.scss index e69de29bb2..e516151d57 100644 --- a/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.scss +++ b/src/app/shared/browse-by/browse-by-starts-with/date/browse-by-starts-with-date.component.scss @@ -0,0 +1,7 @@ +@import '../../../../../styles/variables.scss'; + +// temporary fix for bootstrap 4 beta btn color issue +.btn-secondary { + background-color: $input-bg; + color: $input-color; +} diff --git a/src/app/shared/browse-by/browse-by-starts-with/text/browse-by-starts-with-text.component.html b/src/app/shared/browse-by/browse-by-starts-with/text/browse-by-starts-with-text.component.html index b2144ed9d3..7a1b58e832 100644 --- a/src/app/shared/browse-by/browse-by-starts-with/text/browse-by-starts-with-text.component.html +++ b/src/app/shared/browse-by/browse-by-starts-with/text/browse-by-starts-with-text.component.html @@ -1,4 +1 @@ -

Test Starts-With Text

-

- {{element}}, -

+ diff --git a/src/config/browse-by-config.interface.ts b/src/config/browse-by-config.interface.ts new file mode 100644 index 0000000000..1bc804c3b8 --- /dev/null +++ b/src/config/browse-by-config.interface.ts @@ -0,0 +1,7 @@ +import { Config } from './config.interface'; + +export interface BrowseByConfig extends Config { + oneYearLimit: number; + fiveYearLimit: number; + defaultLowerLimit: number; +} diff --git a/src/config/global-config.interface.ts b/src/config/global-config.interface.ts index 6d1d4da6f9..38532941a8 100644 --- a/src/config/global-config.interface.ts +++ b/src/config/global-config.interface.ts @@ -5,6 +5,7 @@ import { UniversalConfig } from './universal-config.interface'; import { INotificationBoardOptions } from './notifications-config.interfaces'; import { FormConfig } from './form-config.interfaces'; import {LangConfig} from './lang-config.interface'; +import { BrowseByConfig } from './browse-by-config.interface'; export interface GlobalConfig extends Config { ui: ServerConfig; @@ -19,4 +20,5 @@ export interface GlobalConfig extends Config { debug: boolean; defaultLanguage: string; languages: LangConfig[]; + browseBy: BrowseByConfig; }