62063: Configurable browse-by

This commit is contained in:
Kristof De Langhe
2019-04-30 16:24:22 +02:00
parent 684b74e33d
commit 67e5578bba
12 changed files with 128 additions and 15 deletions

View File

@@ -149,7 +149,26 @@ module.exports = {
// Limit for years to display using jumps of five years (current year - fiveYearLimit) // Limit for years to display using jumps of five years (current year - fiveYearLimit)
fiveYearLimit: 30, fiveYearLimit: 30,
// The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items) // The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
defaultLowerLimit: 1900 defaultLowerLimit: 1900,
types: [
{
metadata: 'title',
type: 'title'
},
{
metadata: 'dateissued',
type: 'date',
metadataField: 'dc.date.issued'
},
{
metadata: 'author',
type: 'metadata'
},
{
metadata: 'subject',
type: 'metadata'
}
]
}, },
item: { item: {
edit: { edit: {

View File

@@ -13,6 +13,7 @@ import { BrowseService } from '../../core/browse/browse.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator';
@Component({ @Component({
selector: 'ds-browse-by-date-page', selector: 'ds-browse-by-date-page',
@@ -24,6 +25,7 @@ import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
* A metadata definition is a short term used to describe one or multiple metadata fields. * A metadata definition is a short term used to describe one or multiple metadata fields.
* An example would be 'dateissued' for 'dc.date.issued' * An example would be 'dateissued' for 'dc.date.issued'
*/ */
@rendersBrowseBy(BrowseByType.Date)
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
/** /**
@@ -78,8 +80,9 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
let lowerLimit = this.config.browseBy.defaultLowerLimit; let lowerLimit = this.config.browseBy.defaultLowerLimit;
if (hasValue(firstItemRD.payload)) { if (hasValue(firstItemRD.payload)) {
const date = firstItemRD.payload.firstMetadataValue(metadataField); const date = firstItemRD.payload.firstMetadataValue(metadataField);
if (hasValue(date) && hasValue(+date.split('-')[0])) { if (hasValue(date)) {
lowerLimit = +date.split('-')[0]; const dateObj = new Date(date);
lowerLimit = dateObj.getFullYear();
} }
} }
const options = []; const options = [];

View File

@@ -15,6 +15,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator';
@Component({ @Component({
selector: 'ds-browse-by-metadata-page', selector: 'ds-browse-by-metadata-page',
@@ -26,6 +27,7 @@ import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
* A metadata definition is a short term used to describe one or multiple metadata fields. * A metadata definition is a short term used to describe one or multiple metadata fields.
* An example would be 'author' for 'dc.contributor.*' * An example would be 'author' for 'dc.contributor.*'
*/ */
@rendersBrowseBy(BrowseByType.Metadata)
export class BrowseByMetadataPageComponent implements OnInit { export class BrowseByMetadataPageComponent implements OnInit {
/** /**

View File

@@ -0,0 +1,29 @@
import { hasNoValue } from '../../shared/empty.util';
export enum BrowseByType {
Title = 'title',
Metadata = 'metadata',
Date = 'date'
}
export const DEFAULT_BROWSE_BY_TYPE = BrowseByType.Metadata;
const map = new Map();
export function rendersBrowseBy(browseByType: BrowseByType) {
return function decorator(component: any) {
if (hasNoValue(map.get(browseByType))) {
map.set(browseByType, component);
} else {
throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}"`);
}
};
}
export function getComponentByBrowseByType(browseByType) {
const comp = map.get(browseByType);
if (hasNoValue(comp)) {
map.get(DEFAULT_BROWSE_BY_TYPE);
}
return comp;
}

View File

@@ -0,0 +1 @@
<ng-container *ngComponentOutlet="getComponent() | async"></ng-container>

View File

@@ -0,0 +1,36 @@
import { Component, Inject, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interface';
import { map, tap } from 'rxjs/operators';
import { getComponentByBrowseByType } from './browse-by-decorator';
@Component({
selector: 'ds-browse-by-switcher',
templateUrl: './browse-by-switcher.component.html'
})
export class BrowseBySwitcherComponent implements OnInit {
browseByTypeConfig: Observable<BrowseByTypeConfig>;
public constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
protected route: ActivatedRoute) {
}
ngOnInit(): void {
this.browseByTypeConfig = this.route.params.pipe(
map((params) => {
const metadata = params.metadata;
return this.config.browseBy.types.find((config: BrowseByTypeConfig) => config.metadata === metadata);
})
);
}
getComponent() {
return this.browseByTypeConfig.pipe(
map((config: BrowseByTypeConfig) => getComponentByBrowseByType(config.type))
);
}
}

View File

@@ -1,6 +1,5 @@
import { combineLatest as observableCombineLatest } from 'rxjs'; import { combineLatest as observableCombineLatest } from 'rxjs';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ItemDataService } from '../../core/data/item-data.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { import {
@@ -11,6 +10,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { BrowseService } from '../../core/browse/browse.service'; import { BrowseService } from '../../core/browse/browse.service';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator';
@Component({ @Component({
selector: 'ds-browse-by-title-page', selector: 'ds-browse-by-title-page',
@@ -20,6 +20,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
/** /**
* Component for browsing items by title (dc.title) * Component for browsing items by title (dc.title)
*/ */
@rendersBrowseBy(BrowseByType.Title)
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
public constructor(protected route: ActivatedRoute, public constructor(protected route: ActivatedRoute,

View File

@@ -1,11 +1,12 @@
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
import { hasValue } from '../shared/empty.util'; import { hasNoValue, hasValue } from '../shared/empty.util';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getSucceededRemoteData } from '../core/shared/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../config';
@Injectable() @Injectable()
/** /**
@@ -13,14 +14,21 @@ import { of as observableOf } from 'rxjs';
*/ */
export class BrowseByGuard implements CanActivate { export class BrowseByGuard implements CanActivate {
constructor(protected dsoService: DSpaceObjectDataService, constructor(@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
protected dsoService: DSpaceObjectDataService,
protected translate: TranslateService) { protected translate: TranslateService) {
} }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
const title = route.data.title; const title = route.data.title;
const metadata = route.params.metadata || route.queryParams.metadata || route.data.metadata; const metadata = route.params.metadata || route.queryParams.metadata || route.data.metadata;
const metadataField = route.data.metadataField; let metadataField = route.data.metadataField;
if (hasNoValue(metadataField) && hasValue(metadata)) {
const config = this.config.browseBy.types.find((conf) => conf.metadata === metadata);
if (hasValue(config) && hasValue(config.metadataField)) {
metadataField = config.metadataField;
}
}
const scope = route.queryParams.scope; const scope = route.queryParams.scope;
const value = route.queryParams.value; const value = route.queryParams.value;
const metadataTranslated = this.translate.instant('browse.metadata.' + metadata); const metadataTranslated = this.translate.instant('browse.metadata.' + metadata);

View File

@@ -1,16 +1,12 @@
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowseByTitlePageComponent } from './+browse-by-title-page/browse-by-title-page.component';
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.component';
import { BrowseByGuard } from './browse-by-guard'; import { BrowseByGuard } from './browse-by-guard';
import { BrowseBySwitcherComponent } from './+browse-by-switcher/browse-by-switcher.component';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ path: 'title', component: BrowseByTitlePageComponent, canActivate: [BrowseByGuard], data: { metadata: 'title', title: 'browse.title' } }, { path: ':metadata', component: BrowseBySwitcherComponent, canActivate: [BrowseByGuard], data: { title: 'browse.title' } }
{ path: 'dateissued', component: BrowseByDatePageComponent, canActivate: [BrowseByGuard], data: { metadata: 'dateissued', metadataField: 'dc.date.issued', title: 'browse.title' } },
{ path: ':metadata', component: BrowseByMetadataPageComponent, canActivate: [BrowseByGuard], data: { title: 'browse.title' } }
]) ])
] ]
}) })

View File

@@ -8,6 +8,7 @@ import { BrowseService } from '../core/browse/browse.service';
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component'; import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.component'; import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.component';
import { BrowseByGuard } from './browse-by-guard'; import { BrowseByGuard } from './browse-by-guard';
import { BrowseBySwitcherComponent } from './+browse-by-switcher/browse-by-switcher.component';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -18,12 +19,18 @@ import { BrowseByGuard } from './browse-by-guard';
declarations: [ declarations: [
BrowseByTitlePageComponent, BrowseByTitlePageComponent,
BrowseByMetadataPageComponent, BrowseByMetadataPageComponent,
BrowseByDatePageComponent BrowseByDatePageComponent,
BrowseBySwitcherComponent
], ],
providers: [ providers: [
ItemDataService, ItemDataService,
BrowseService, BrowseService,
BrowseByGuard BrowseByGuard
],
entryComponents: [
BrowseByTitlePageComponent,
BrowseByMetadataPageComponent,
BrowseByDatePageComponent
] ]
}) })
export class BrowseByModule { export class BrowseByModule {

View File

@@ -1,4 +1,5 @@
import { Config } from './config.interface'; import { Config } from './config.interface';
import { BrowseByTypeConfig } from './browse-by-type-config.interface';
/** /**
* Config that determines how the dropdown list of years are created for browse-by-date components * Config that determines how the dropdown list of years are created for browse-by-date components
@@ -18,4 +19,6 @@ export interface BrowseByConfig extends Config {
* The absolute lowest year to display in the dropdown when no lowest date can be found for all items * The absolute lowest year to display in the dropdown when no lowest date can be found for all items
*/ */
defaultLowerLimit: number; defaultLowerLimit: number;
types: BrowseByTypeConfig[];
} }

View File

@@ -0,0 +1,8 @@
import { Config } from './config.interface';
import { BrowseByType } from '../app/+browse-by/+browse-by-switcher/browse-by-decorator';
export interface BrowseByTypeConfig extends Config {
metadata: string;
type: BrowseByType;
metadataField: string;
}