59695: Starts-with implementation for browse-by date

This commit is contained in:
Kristof De Langhe
2019-02-12 10:40:47 +01:00
parent d80233074f
commit cdcacedfae
11 changed files with 190 additions and 34 deletions

View File

@@ -72,5 +72,14 @@ module.exports = {
code: 'nl', code: 'nl',
label: 'Nederlands', label: 'Nederlands',
active: false, 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
}
}; };

View File

@@ -281,6 +281,11 @@
}, },
"browse": { "browse": {
"title": "Browsing {{ collection }} by {{ field }} {{ value }}", "title": "Browsing {{ collection }} by {{ field }} {{ value }}",
"startsWith": {
"choose_year": "(Choose year)",
"type_year": "Or type in a year:",
"submit": "Go"
},
"metadata": { "metadata": {
"title": "Title", "title": "Title",
"author": "Author", "author": "Author",

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { import {
BrowseByMetadataPageComponent, BrowseByMetadataPageComponent,
browseParamsToOptions browseParamsToOptions
@@ -6,6 +6,14 @@ import {
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
import { BrowseByStartsWithType } from '../../shared/browse-by/browse-by.component'; 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({ @Component({
selector: 'ds-browse-by-date-page', selector: 'ds-browse-by-date-page',
@@ -19,8 +27,15 @@ import { BrowseByStartsWithType } from '../../shared/browse-by/browse-by.compone
*/ */
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
oneYearLimit = 10; defaultMetadataField = 'dc.date.issued';
fiveYearLimit = 30;
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 { ngOnInit(): void {
this.startsWithType = BrowseByStartsWithType.date; this.startsWithType = BrowseByStartsWithType.date;
@@ -34,17 +49,37 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
return Object.assign({}, params, queryParams, data); return Object.assign({}, params, queryParams, data);
}) })
.subscribe((params) => { .subscribe((params) => {
const metadataField = params.metadataField || this.defaultMetadataField;
this.metadata = params.metadata || this.defaultMetadata; this.metadata = params.metadata || this.defaultMetadata;
this.startsWith = +params.startsWith || params.startsWith; this.startsWith = +params.startsWith || params.startsWith;
const searchOptions = browseParamsToOptions(params, Object.assign({}), this.sortConfig, this.metadata); const searchOptions = browseParamsToOptions(params, Object.assign({}), this.sortConfig, this.metadata);
this.updatePageWithItems(searchOptions, this.value); this.updatePageWithItems(searchOptions, this.value);
this.updateParent(params.scope); this.updateParent(params.scope);
this.updateStartsWithOptions(this.metadata, metadataField, params.scope);
})); }));
}
updateStartsWithOptions(definition: string, metadataField: string, scope?: string) {
this.subs.push(
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<PaginatedList<Item>>) => {
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 options = [];
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const oneYearBreak = Math.floor((currentYear - this.oneYearLimit) / 5) * 5; const oneYearBreak = Math.floor((currentYear - this.config.browseBy.oneYearLimit) / 5) * 5;
const fiveYearBreak = Math.floor((currentYear - this.fiveYearLimit) / 10) * 10; const fiveYearBreak = Math.floor((currentYear - this.config.browseBy.fiveYearLimit) / 10) * 10;
const lowerLimit = 1900; // Hardcoded atm, this should be the lowest date issued if (lowerLimit <= fiveYearBreak) {
lowerLimit -= 10;
} else if (lowerLimit <= oneYearBreak) {
lowerLimit -= 5;
} else {
lowerLimit -= 1;
}
let i = currentYear; let i = currentYear;
while (i > lowerLimit) { while (i > lowerLimit) {
options.push(i); options.push(i);
@@ -56,7 +91,11 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
i--; i--;
} }
} }
console.log(options); if (isNotEmpty(options)) {
this.startsWithOptions = options;
}
})
);
} }
} }

View File

@@ -8,7 +8,7 @@ import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ path: 'title', component: BrowseByTitlePageComponent }, { 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 } { path: ':metadata', component: BrowseByMetadataPageComponent }
]) ])
] ]

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators'; import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators';
import { import {
ensureArrayHasValue, ensureArrayHasValue, hasValue,
hasValueOperator, hasValueOperator,
isEmpty, isEmpty,
isNotEmpty, isNotEmpty,
@@ -162,6 +162,28 @@ export class BrowseService {
); );
} }
getFirstItemFor(definition: string, scope?: string): Observable<RemoteData<PaginatedList<Item>>> {
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<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> { getPrevBrowseItems(items: RemoteData<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> {
return observableOf(items.payload.prev).pipe( return observableOf(items.payload.prev).pipe(
getBrowseItemsFor(this.requestService, this.responseCache, this.rdb) getBrowseItemsFor(this.requestService, this.responseCache, this.rdb)

View File

@@ -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 { export class BrowseByStartsWithAbstractComponent implements OnInit, OnDestroy {
public constructor(@Inject('startsWithOptions') public startsWithOptions: any[]) { 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());
} }
} }

View File

@@ -1,4 +1,22 @@
<h3>Test Starts-With Date</h3> <form class="w-100" [formGroup]="formData" (ngSubmit)="submitForm(formData.value)">
<p> <div class="container">
<span *ngFor="let element of startsWithOptions">{{element}}, </span> <div class="row mb-2">
</p> <select class="form-control col-xs-5 col-sm-3" (change)="setStartsWith($event)">
<option [value]="-1" [selected]="!startsWith">
{{'browse.startsWith.choose_year' | translate}}
</option>
<option *ngFor="let option of startsWithOptions"
[value]="option"
[selected]="option === startsWith || option === +startsWith ? 'selected': null">
{{option}}
</option>
</select>
<div class="form-group input-group col-xs-7 col-sm-6">
<input class="form-control" placeholder="{{'browse.startsWith.type_year' | translate}}" type="number" name="startsWith" formControlName="startsWith" [value]="+startsWith" />
<span class="input-group-append">
<button class="btn btn-secondary" type="submit">{{'browse.startsWith.submit' | translate}}</button>
</span>
</div>
</div>
</div>
</form>

View File

@@ -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;
}

View File

@@ -1,4 +1 @@
<h3>Test Starts-With Text</h3> <!-- To be implemented -->
<p>
<span *ngFor="let element of startsWithOptions">{{element}}, </span>
</p>

View File

@@ -0,0 +1,7 @@
import { Config } from './config.interface';
export interface BrowseByConfig extends Config {
oneYearLimit: number;
fiveYearLimit: number;
defaultLowerLimit: number;
}

View File

@@ -5,6 +5,7 @@ import { UniversalConfig } from './universal-config.interface';
import { INotificationBoardOptions } from './notifications-config.interfaces'; import { INotificationBoardOptions } from './notifications-config.interfaces';
import { FormConfig } from './form-config.interfaces'; import { FormConfig } from './form-config.interfaces';
import {LangConfig} from './lang-config.interface'; import {LangConfig} from './lang-config.interface';
import { BrowseByConfig } from './browse-by-config.interface';
export interface GlobalConfig extends Config { export interface GlobalConfig extends Config {
ui: ServerConfig; ui: ServerConfig;
@@ -19,4 +20,5 @@ export interface GlobalConfig extends Config {
debug: boolean; debug: boolean;
defaultLanguage: string; defaultLanguage: string;
languages: LangConfig[]; languages: LangConfig[];
browseBy: BrowseByConfig;
} }