add the ability to mix text based metadata fields, authority controlled fields and typed items in the same section

This commit is contained in:
Art Lowel
2019-01-21 18:27:07 +01:00
parent 8ae8498ab1
commit 57999ad382
54 changed files with 833 additions and 98 deletions

View File

@@ -207,7 +207,7 @@
"f.dateIssued.max": "End date",
"f.subject": "Subject",
"f.has_content_in_original_bundle": "Has files",
"f.entityType": "Item Type"
"f.entityType": "Entity Type"
},
"filter": {
"show-more": "Show more",
@@ -237,8 +237,8 @@
"head": "Has files"
},
"entityType": {
"placeholder": "Item Type",
"head": "Item Type"
"placeholder": "Entity Type",
"head": "Entity Type"
}
}
}

View File

@@ -4,6 +4,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
import { MetadataValuesComponent } from './metadata-values.component';
import { By } from '@angular/platform-browser';
import { Metadatum } from '../../../core/shared/metadatum.model';
let comp: MetadataValuesComponent;
let fixture: ComponentFixture<MetadataValuesComponent>;
@@ -23,7 +24,7 @@ const mockMetadata = [
key: 'journal.identifier.description',
language: 'en_US',
value: 'desc'
}];
}] as Metadatum[];
const mockSeperator = '<br/>';
const mockLabel = 'fake.message';

View File

@@ -29,6 +29,7 @@ import { JournalComponent } from './simple/item-types/journal/journal.component'
import { JournalVolumeComponent } from './simple/item-types/journal-volume/journal-volume.component';
import { JournalIssueComponent } from './simple/item-types/journal-issue/journal-issue.component';
import { ItemComponent } from './simple/item-types/shared/item.component';
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
@NgModule({
imports: [
@@ -61,7 +62,8 @@ import { ItemComponent } from './simple/item-types/shared/item.component';
GenericItemPageFieldComponent,
JournalComponent,
JournalIssueComponent,
JournalVolumeComponent
JournalVolumeComponent,
MetadataRepresentationListComponent
],
entryComponents: [
PublicationComponent,

View File

@@ -1,7 +1,7 @@
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
<div *ngIf="itemRD?.payload as item">
<ds-item-type-switcher [object]="item" [viewMode]="ElementViewMode.Full"></ds-item-type-switcher>
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
</div>
</div>
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>

View File

@@ -15,6 +15,8 @@ import { fadeInOut } from '../../shared/animations/fade';
import { hasValue } from '../../shared/empty.util';
import * as viewMode from '../../shared/view-mode';
export const VIEW_MODE_FULL = 'full';
/**
* This component renders a simple item page.
* The route parameter 'id' is used to request the item it represents.
@@ -46,9 +48,8 @@ export class ItemPageComponent implements OnInit {
/**
* The view-mode we're currently on
* @type {ElementViewMode}
*/
ElementViewMode = viewMode.ElementViewMode;
viewMode = VIEW_MODE_FULL;
constructor(
private route: ActivatedRoute,

View File

@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('JournalIssue', ElementViewMode.Full)
@rendersItemType('JournalIssue', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal-issue',
styleUrls: ['./journal-issue.component.scss'],

View File

@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('JournalVolume', ElementViewMode.Full)
@rendersItemType('JournalVolume', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal-volume',
styleUrls: ['./journal-volume.component.scss'],

View File

@@ -4,11 +4,11 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('Journal', ElementViewMode.Full)
@rendersItemType('Journal', VIEW_MODE_FULL)
@Component({
selector: 'ds-journal',
styleUrls: ['./journal.component.scss'],

View File

@@ -3,12 +3,12 @@ import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ElementViewMode } from '../../../../shared/view-mode';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('OrgUnit', ElementViewMode.Full)
@rendersItemType('OrgUnit', VIEW_MODE_FULL)
@Component({
selector: 'ds-orgunit',
styleUrls: ['./orgunit.component.scss'],

View File

@@ -4,12 +4,12 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('Person', ElementViewMode.Full)
@rendersItemType('Person', VIEW_MODE_FULL)
@Component({
selector: 'ds-person',
styleUrls: ['./person.component.scss'],

View File

@@ -3,12 +3,12 @@ import { Observable } from 'rxjs';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ElementViewMode } from '../../../../shared/view-mode';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { isNotEmpty } from '../../../../shared/empty.util';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('Project', ElementViewMode.Full)
@rendersItemType('Project', VIEW_MODE_FULL)
@Component({
selector: 'ds-project',
styleUrls: ['./project.component.scss'],

View File

@@ -9,10 +9,10 @@
<ds-item-page-author-field *ngIf="!(authors$ | async)" [item]="item"></ds-item-page-author-field>
</div>
<div class="col-xs-12 col-md-6">
<ds-related-items
[items]="authors$ | async"
[label]="'relationships.isAuthorOf' | translate">
</ds-related-items>
<ds-metadata-representation-list
[label]="'relationships.isAuthorOf' | translate"
[representations]="authors$ | async">
</ds-metadata-representation-list>
<ds-related-items
[items]="projects$ | async"
[label]="'relationships.isProjectOf' | translate">

View File

@@ -7,11 +7,12 @@ import {
rendersItemType
} from '../../../../shared/items/item-type-decorator';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ElementViewMode } from '../../../../shared/view-mode';
import { ItemComponent, filterRelationsByTypeLabel, relationsToItems } from '../shared/item.component';
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { VIEW_MODE_FULL } from '../../item-page.component';
@rendersItemType('Publication', ElementViewMode.Full)
@rendersItemType(DEFAULT_ITEM_TYPE, ElementViewMode.Full)
@rendersItemType('Publication', VIEW_MODE_FULL)
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_FULL)
@Component({
selector: 'ds-publication',
styleUrls: ['./publication.component.scss'],
@@ -22,7 +23,7 @@ export class PublicationComponent extends ItemComponent implements OnInit {
/**
* The authors related to this publication
*/
authors$: Observable<Item[]>;
authors$: Observable<MetadataRepresentation[]>;
/**
* The projects related to this publication
@@ -51,10 +52,7 @@ export class PublicationComponent extends ItemComponent implements OnInit {
if (this.resolvedRelsAndTypes$) {
this.authors$ = this.resolvedRelsAndTypes$.pipe(
filterRelationsByTypeLabel('isAuthorOfPublication'),
relationsToItems(this.item.id, this.ids)
);
this.authors$ = this.buildRepresentations('Person', 'dc.contributor.author', this.ids);
this.projects$ = this.resolvedRelsAndTypes$.pipe(
filterRelationsByTypeLabel('isProjectOfPublication'),

View File

@@ -16,8 +16,18 @@ import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { compareArraysUsing, compareArraysUsingIds } from './item.component';
import { compareArraysUsing, compareArraysUsingIds, ItemComponent } from './item.component';
import { of as observableOf } from 'rxjs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ItemPageComponent } from '../../item-page.component';
import { VarDirective } from '../../../../shared/utils/var.directive';
import { ActivatedRoute } from '@angular/router';
import { MetadataService } from '../../../../core/metadata/metadata.service';
import { of } from 'rxjs/internal/observable/of';
import { Observable } from 'rxjs/internal/Observable';
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
/**
* Create a generic test for an item-page-fields component using a mockItem and the type of component
@@ -306,4 +316,116 @@ describe('ItemComponent', () => {
});
});
describe('when calling buildRepresentations', () => {
let comp: ItemComponent;
let fixture: ComponentFixture<ItemComponent>;
const metadataField = 'dc.contributor.author';
const mockItem = Object.assign(new Item(), {
id: '1',
uuid: '1',
metadata: [
{
key: metadataField,
value: 'Second value',
place: 1
},
{
key: metadataField,
value: 'Third value',
place: 2,
authority: 'virtual::123'
},
{
key: metadataField,
value: 'First value',
place: 0
},
{
key: metadataField,
value: 'Fourth value',
place: 3,
authority: '123'
}
],
relationships: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
Object.assign(new Relationship(), {
uuid: '123',
id: '123',
leftId: '1',
rightId: '2',
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType()))
})
])))
});
const relatedItem = Object.assign(new Item(), {
id: '2',
metadata: [
{
key: 'dc.title',
value: 'related item'
}
]
});
const mockItemDataService = {
findById: (id) => {
if (id === relatedItem.id) {
return observableOf(new RemoteData(false, false, true, null, relatedItem))
}
}
} as ItemDataService;
let representations: Observable<MetadataRepresentation[]>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: MockTranslateLoader
}
}), BrowserAnimationsModule],
declarations: [ItemComponent, VarDirective],
providers: [
{provide: ITEM, useValue: mockItem}
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
representations = comp.buildRepresentations('bogus', metadataField, mockItemDataService);
}));
it('should contain exactly 4 metadata-representations', () => {
representations.subscribe((reps: MetadataRepresentation[]) => {
expect(reps.length).toEqual(4);
});
});
it('should have all the representations in the correct order', () => {
representations.subscribe((reps: MetadataRepresentation[]) => {
expect(reps[0].getPrimaryValue()).toEqual('First value');
expect(reps[1].getPrimaryValue()).toEqual('Second value');
expect(reps[2].getPrimaryValue()).toEqual('related item');
expect(reps[3].getPrimaryValue()).toEqual('Fourth value');
});
});
it('should have created the correct MetadatumRepresentation and ItemMetadataRepresentation objects for the correct Metadata', () => {
representations.subscribe((reps: MetadataRepresentation[]) => {
expect(reps[0] instanceof MetadatumRepresentation).toEqual(true);
expect(reps[1] instanceof MetadatumRepresentation).toEqual(true);
expect(reps[2] instanceof ItemMetadataRepresentation).toEqual(true);
expect(reps[3] instanceof MetadatumRepresentation).toEqual(true);
});
});
})
});

View File

@@ -7,9 +7,14 @@ import { RemoteData } from '../../../../core/data/remote-data';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { Item } from '../../../../core/shared/item.model';
import { getRemoteDataPayload } from '../../../../core/shared/operators';
import { hasValue } from '../../../../shared/empty.util';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
import { hasNoValue, hasValue } from '../../../../shared/empty.util';
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { Metadatum } from '../../../../core/shared/metadatum.model';
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { of } from 'rxjs/internal/observable/of';
/**
* Operator for comparing arrays using a mapping function
@@ -82,6 +87,41 @@ export const relationsToItems = (thisId: string, ids: ItemDataService) =>
distinctUntilChanged(compareArraysUsingIds()),
);
/**
* Operator for turning a list of relationships into a list of metadatarepresentations given the original metadata
* @param thisId The id of the parent item
* @param itemType The type of relation this list resembles (for creating representations)
* @param metadata The list of original Metadatum objects
* @param ids The ItemDataService to use for fetching Items from the Rest API
*/
export const relationsToRepresentations = (thisId: string, itemType: string, metadata: Metadatum[], ids: ItemDataService) =>
(source: Observable<Relationship[]>): Observable<MetadataRepresentation[]> =>
source.pipe(
flatMap((rels: Relationship[]) =>
observableZip(
...metadata.map((metadatum: Metadatum) => {
const prefix = 'virtual::';
if (hasValue(metadatum.authority) && metadatum.authority.startsWith(prefix)) {
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.authority.substring(metadatum.authority.indexOf(prefix) + prefix.length));
if (matchingRels.length > 0) {
const matchingRel = matchingRels[0];
let queryId = matchingRel.leftId;
if (matchingRel.leftId === thisId) {
queryId = matchingRel.rightId;
}
return ids.findById(queryId).pipe(
getSucceededRemoteData(),
map((d: RemoteData<Item>) => Object.assign(new ItemMetadataRepresentation(itemType), d.payload))
);
}
} else {
return of(Object.assign(new MetadatumRepresentation(itemType), metadatum));
}
})
)
)
);
@Component({
selector: 'ds-item',
template: ''
@@ -93,7 +133,7 @@ export class ItemComponent implements OnInit {
/**
* Resolved relationships and types together in one observable
*/
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
constructor(
@Inject(ITEM) public item: Item
@@ -125,4 +165,25 @@ export class ItemComponent implements OnInit {
}
}
/**
* Build a list of MetadataRepresentations for the current item. This combines all metadata and relationships of a
* certain type.
* @param itemType The type of item we're building representations of. Used for matching templates.
* @param metadataField The metadata field that resembles the item type.
* @param itemDataService ItemDataService to turn relations into items.
*/
buildRepresentations(itemType: string, metadataField: string, itemDataService: ItemDataService): Observable<MetadataRepresentation[]> {
const metadata = this.item.findMetadataSortedByPlace(metadataField);
const relsCurrentPage$ = this.item.relationships.pipe(
getSucceededRemoteData(),
getRemoteDataPayload(),
map((pl: PaginatedList<Relationship>) => pl.page),
distinctUntilChanged(compareArraysUsingIds())
);
return relsCurrentPage$.pipe(
relationsToRepresentations(this.item.id, itemType, metadata, itemDataService)
);
}
}

View File

@@ -0,0 +1,5 @@
<ds-metadata-field-wrapper *ngIf="representations && representations.length > 0" [label]="label">
<ds-item-type-switcher *ngFor="let rep of representations"
[object]="rep" [viewMode]="viewMode">
</ds-item-type-switcher>
</ds-metadata-field-wrapper>

View File

@@ -0,0 +1,40 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { MetadataRepresentationListComponent } from './metadata-representation-list.component';
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
const itemType = 'type';
const metadataRepresentation1 = new MetadatumRepresentation(itemType);
const metadataRepresentation2 = new ItemMetadataRepresentation(itemType);
const representations = [metadataRepresentation1, metadataRepresentation2];
describe('MetadataRepresentationListComponent', () => {
let comp: MetadataRepresentationListComponent;
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [MetadataRepresentationListComponent],
providers: [],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(MetadataRepresentationListComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(MetadataRepresentationListComponent);
comp = fixture.componentInstance;
comp.representations = representations;
fixture.detectChanges();
}));
it(`should load ${representations.length} item-type-switcher components`, () => {
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
expect(fields.length).toBe(representations.length);
});
});

View File

@@ -0,0 +1,31 @@
import { Component, Input } from '@angular/core';
import * as viewMode from '../../../shared/view-mode';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
export const VIEW_MODE_METADATA = 'metadata';
@Component({
selector: 'ds-metadata-representation-list',
templateUrl: './metadata-representation-list.component.html'
})
/**
* This component is used for displaying metadata
* It expects a list of MetadataRepresentation objects and a label to put on top of the list
*/
export class MetadataRepresentationListComponent {
/**
* A list of metadata-representations to display
*/
@Input() representations: MetadataRepresentation[];
/**
* An i18n label to use as a title for the list
*/
@Input() label: string;
/**
* The view-mode we're currently on
* @type {ElementViewMode}
*/
viewMode = VIEW_MODE_METADATA;
}

View File

@@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import * as viewMode from '../../../shared/view-mode';
export const VIEW_MODE_ELEMENT = 'element';
@Component({
selector: 'ds-related-items',
@@ -26,5 +27,5 @@ export class RelatedItemsComponent {
* The view-mode we're currently on
* @type {ElementViewMode}
*/
ElementViewMode = viewMode.ElementViewMode
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -1,5 +1,5 @@
<ds-metadata-field-wrapper *ngIf="items && items.length > 0" [label]="label">
<ds-item-type-switcher *ngFor="let item of items"
[object]="item" [viewMode]="ElementViewMode.SetElement">
[object]="item" [viewMode]="viewMode">
</ds-item-type-switcher>
</ds-metadata-field-wrapper>

View File

@@ -33,6 +33,7 @@ import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
import { BrowseService } from '../browse/browse.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { EmptyError } from 'rxjs/internal-compatibility';
import { Metadatum } from '../shared/metadatum.model';
/* tslint:disable:max-classes-per-file */
@Component({
@@ -223,7 +224,7 @@ describe('MetadataService', () => {
key: 'dc.publisher',
language: 'en_US',
value: 'Mock Publisher'
});
} as Metadatum);
return publishedMockItem;
}

View File

@@ -1,5 +1,5 @@
import { Metadatum } from './metadatum.model'
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { hasNoValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data';
import { ResourceType } from './resource-type';
@@ -91,4 +91,23 @@ export class DSpaceObject implements CacheableObject, ListableObject {
});
}
/**
* Find metadata on a specific field and order all of them using their "place" property.
* @param key
*/
findMetadataSortedByPlace(key: string): Metadatum[] {
return this.filterMetadata([key]).sort((a: Metadatum, b: Metadatum) => {
if (hasNoValue(a.place) && hasNoValue(b.place)) {
return 0;
}
if (hasNoValue(a.place)) {
return -1;
}
if (hasNoValue(b.place)) {
return 1;
}
return a.place - b.place;
});
}
}

View File

@@ -0,0 +1,36 @@
import { MetadataRepresentationType } from '../metadata-representation.model';
import { ItemMetadataRepresentation, ItemTypeToPrimaryValue } from './item-metadata-representation.model';
import { Item } from '../../item.model';
import { Metadatum } from '../../metadatum.model';
describe('ItemMetadataRepresentation', () => {
const valuePrefix = 'Test value for ';
const item = new Item();
let itemMetadataRepresentation: ItemMetadataRepresentation;
item.metadata = Object.keys(ItemTypeToPrimaryValue).map((key: string) => {
return Object.assign(new Metadatum(), {
key: ItemTypeToPrimaryValue[key],
value: `${valuePrefix}${ItemTypeToPrimaryValue[key]}`
});
});
for (const itemType of Object.keys(ItemTypeToPrimaryValue)) {
describe(`when creating an ItemMetadataRepresentation with item-type "${itemType}"`, () => {
beforeEach(() => {
itemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(itemType), item);
});
it('should have a representation type of item', () => {
expect(itemMetadataRepresentation.representationType).toEqual(MetadataRepresentationType.Item);
});
it('should return the correct value when calling getPrimaryValue', () => {
expect(itemMetadataRepresentation.getPrimaryValue()).toEqual(`${valuePrefix}${ItemTypeToPrimaryValue[itemType]}`);
});
it('should return the correct item type', () => {
expect(itemMetadataRepresentation.itemType).toEqual(itemType);
});
});
}
});

View File

@@ -0,0 +1,48 @@
import { Item } from '../../item.model';
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
import { hasValue } from '../../../../shared/empty.util';
/**
* An object to convert item types into the metadata field it should render for the item's primary value
*/
export const ItemTypeToPrimaryValue = {
Default: 'dc.title',
Person: 'dc.contributor.author'
};
/**
* This class defines the way the item it extends should be represented as metadata
*/
export class ItemMetadataRepresentation extends Item implements MetadataRepresentation {
/**
* The type of item this item can be represented as
*/
itemType: string;
constructor(itemType: string) {
super();
this.itemType = itemType;
}
/**
* Fetch the way this item should be rendered as in a list
*/
get representationType(): MetadataRepresentationType {
return MetadataRepresentationType.Item;
}
/**
* Get the value to display, depending on the itemType
*/
getPrimaryValue(): string {
let metadata;
if (hasValue(ItemTypeToPrimaryValue[this.itemType])) {
metadata = ItemTypeToPrimaryValue[this.itemType];
} else {
metadata = ItemTypeToPrimaryValue.Default;
}
return this.findMetadata(metadata);
}
}

View File

@@ -0,0 +1,31 @@
/**
* An Enum defining the representation type of metadata
*/
export enum MetadataRepresentationType {
None = 'none',
Item = 'item',
AuthorityControlled = 'authority_controlled',
PlainText = 'plain_text'
}
/**
* An interface containing information about how we should represent certain metadata
*/
export interface MetadataRepresentation {
/**
* The type of item this metadata is representing
* e.g. 'Person'
* This can be used for template matching
*/
itemType: string;
/**
* How we should render the metadata in a list
*/
representationType: MetadataRepresentationType,
/**
* Fetches the primary value to be displayed
*/
getPrimaryValue(): string
}

View File

@@ -0,0 +1,54 @@
import { Metadatum } from '../../metadatum.model';
import { MetadatumRepresentation } from './metadatum-representation.model';
import { MetadataRepresentationType } from '../metadata-representation.model';
describe('MetadatumRepresentation', () => {
const itemType = 'Person';
const normalMetadatum = Object.assign(new Metadatum(), {
key: 'dc.contributor.author',
value: 'Test Author'
});
const authorityMetadatum = Object.assign(new Metadatum(), {
key: 'dc.contributor.author',
value: 'Test Authority Author',
authority: '1234'
});
let metadatumRepresentation: MetadatumRepresentation;
describe('when creating a MetadatumRepresentation based on a standard Metadatum object', () => {
beforeEach(() => {
metadatumRepresentation = Object.assign(new MetadatumRepresentation(itemType), normalMetadatum);
});
it('should have a representation type of plain text', () => {
expect(metadatumRepresentation.representationType).toEqual(MetadataRepresentationType.PlainText);
});
it('should return the correct value when calling getPrimaryValue', () => {
expect(metadatumRepresentation.getPrimaryValue()).toEqual(normalMetadatum.value);
});
it('should return the correct item type', () => {
expect(metadatumRepresentation.itemType).toEqual(itemType);
});
});
describe('when creating a MetadatumRepresentation based on an authority controlled Metadatum object', () => {
beforeEach(() => {
metadatumRepresentation = Object.assign(new MetadatumRepresentation(itemType), authorityMetadatum);
});
it('should have a representation type of plain text', () => {
expect(metadatumRepresentation.representationType).toEqual(MetadataRepresentationType.AuthorityControlled);
});
it('should return the correct value when calling getPrimaryValue', () => {
expect(metadatumRepresentation.getPrimaryValue()).toEqual(authorityMetadatum.value);
});
it('should return the correct item type', () => {
expect(metadatumRepresentation.itemType).toEqual(itemType);
});
});
});

View File

@@ -0,0 +1,38 @@
import { Metadatum } from '../../metadatum.model';
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
import { hasValue } from '../../../../shared/empty.util';
/**
* This class defines the way the metadatum it extends should be represented
*/
export class MetadatumRepresentation extends Metadatum implements MetadataRepresentation {
/**
* The type of item this metadatum can be represented as
*/
itemType: string;
constructor(itemType: string) {
super();
this.itemType = itemType;
}
/**
* Fetch the way this metadatum should be rendered as in a list
*/
get representationType(): MetadataRepresentationType {
if (hasValue(this.authority)) {
return MetadataRepresentationType.AuthorityControlled;
} else {
return MetadataRepresentationType.PlainText;
}
}
/**
* Get the value to display
*/
getPrimaryValue(): string {
return this.value;
}
}

View File

@@ -20,4 +20,17 @@ export class Metadatum {
@autoserialize
value: string;
/**
* The place of this Metadatum within his list of metadata
* This is used to render metadata in a specific custom order
*/
@autoserialize
place: number;
/**
* The authority key used for authority-controlled metadata
*/
@autoserialize
authority: string;
}

View File

@@ -1,36 +1,60 @@
import { hasNoValue, hasValue } from '../empty.util';
import { ElementViewMode } from '../view-mode';
import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model';
import { VIEW_MODE_ELEMENT } from '../../+item-page/simple/related-items/related-items-component';
export const DEFAULT_ITEM_TYPE = 'Default';
export const DEFAULT_VIEW_MODE = VIEW_MODE_ELEMENT;
export const NO_REPRESENTATION_TYPE = MetadataRepresentationType.None;
export const DEFAULT_REPRESENTATION_TYPE = MetadataRepresentationType.PlainText;
const map = new Map();
/**
* Decorator used for rendering simple item pages by type and viewMode
* Decorator used for rendering simple item pages by type and viewMode (and optionally a representationType)
* @param type
* @param viewMode
* @param representationType
*/
export function rendersItemType(type: string, viewMode: ElementViewMode) {
export function rendersItemType(type: string, viewMode: string, representationType?: MetadataRepresentationType) {
return function decorator(component: any) {
if (hasNoValue(map.get(viewMode))) {
map.set(viewMode, new Map());
}
if (hasValue(map.get(viewMode).get(type))) {
throw new Error(`There can't be more than one component to render Items of type "${type}" in view mode "${viewMode}"`);
if (hasNoValue(map.get(viewMode).get(type))) {
map.get(viewMode).set(type, new Map());
}
map.get(viewMode).set(type, component);
if (hasNoValue(representationType)) {
representationType = NO_REPRESENTATION_TYPE;
}
if (hasValue(map.get(viewMode).get(type).get(representationType))) {
throw new Error(`There can't be more than one component to render Metadata of type "${type}" in view mode "${viewMode}" with representation type "${representationType}"`);
}
map.get(viewMode).get(type).set(representationType, component);
};
}
/**
* Get the component used for rendering an item by type and viewMode
* Get the component used for rendering an item by type and viewMode (and optionally a representationType)
* @param type
* @param viewMode
* @param representationType
*/
export function getComponentByItemType(type: string, viewMode: ElementViewMode) {
let component = map.get(viewMode).get(type);
if (hasNoValue(component)) {
component = map.get(viewMode).get(DEFAULT_ITEM_TYPE);
export function getComponentByItemType(type: string, viewMode: string, representationType?: MetadataRepresentationType) {
if (hasNoValue(representationType)) {
representationType = NO_REPRESENTATION_TYPE;
}
return component;
if (hasNoValue(map.get(viewMode))) {
viewMode = DEFAULT_VIEW_MODE;
}
if (hasNoValue(map.get(viewMode).get(type))) {
type = DEFAULT_ITEM_TYPE;
}
let representationComponent = map.get(viewMode).get(type).get(representationType);
if (hasNoValue(representationComponent)) {
representationComponent = map.get(viewMode).get(type).get(DEFAULT_REPRESENTATION_TYPE);
}
if (hasNoValue(representationComponent)) {
representationComponent = map.get(viewMode).get(type).get(NO_REPRESENTATION_TYPE);
}
return representationComponent;
}

View File

@@ -8,8 +8,10 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { RemoteData } from '../../../core/data/remote-data';
import * as decorator from '../item-type-decorator';
import { getComponentByItemType } from '../item-type-decorator';
import { ElementViewMode } from '../../view-mode';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import createSpy = jasmine.createSpy;
import { VIEW_MODE_FULL } from '../../../+item-page/simple/item-page.component';
import { VIEW_MODE_METADATA } from '../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
const relationType = 'type';
const mockItem: Item = Object.assign(new Item(), {
@@ -26,7 +28,8 @@ const mockItem: Item = Object.assign(new Item(), {
value: relationType
}]
});
const viewMode = ElementViewMode.Full;
const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(relationType), mockItem);
let viewMode = VIEW_MODE_FULL;
describe('ItemTypeSwitcherComponent', () => {
let comp: ItemTypeSwitcherComponent;
@@ -47,6 +50,13 @@ describe('ItemTypeSwitcherComponent', () => {
spyOnProperty(decorator, 'getComponentByItemType').and.returnValue(createSpy('getComponentByItemType'))
}));
describe('when the injected object is of type Item', () => {
beforeEach(() => {
viewMode = VIEW_MODE_FULL;
comp.object = mockItem;
comp.viewMode = viewMode;
});
describe('when calling getComponent', () => {
beforeEach(() => {
comp.getComponent();
@@ -56,5 +66,24 @@ describe('ItemTypeSwitcherComponent', () => {
expect(decorator.getComponentByItemType).toHaveBeenCalledWith(relationType, viewMode);
});
});
});
describe('when the injected object is of type MetadataRepresentation', () => {
beforeEach(() => {
viewMode = VIEW_MODE_METADATA;
comp.object = mockItemMetadataRepresentation;
comp.viewMode = viewMode;
});
describe('when calling getComponent', () => {
beforeEach(() => {
comp.getComponent();
});
it('should call getComponentByItemType with parameters type, viewMode and representationType', () => {
expect(decorator.getComponentByItemType).toHaveBeenCalledWith(relationType, viewMode, mockItemMetadataRepresentation.representationType);
});
});
});
});

View File

@@ -4,7 +4,7 @@ import { Item } from '../../../core/shared/item.model';
import { hasValue } from '../../empty.util';
import { ItemSearchResult } from '../../object-collection/shared/item-search-result.model';
import { getComponentByItemType } from '../item-type-decorator';
import { ElementViewMode } from '../../view-mode';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
export const ITEM: InjectionToken<string> = new InjectionToken<string>('item');
@@ -18,14 +18,14 @@ export const ITEM: InjectionToken<string> = new InjectionToken<string>('item');
*/
export class ItemTypeSwitcherComponent implements OnInit {
/**
* The item to determine the component for
* The item or metadata to determine the component for
*/
@Input() object: Item | SearchResult<Item>;
@Input() object: Item | SearchResult<Item> | MetadataRepresentation;
/**
* The preferred view-mode to display
*/
@Input() viewMode: ElementViewMode;
@Input() viewMode: string;
/**
* The object injector used to inject the item into the child component
@@ -48,6 +48,11 @@ export class ItemTypeSwitcherComponent implements OnInit {
* @returns {string}
*/
getComponent(): string {
if (hasValue((this.object as any).representationType)) {
const metadataRepresentation = this.object as MetadataRepresentation;
return getComponentByItemType(metadataRepresentation.itemType, this.viewMode, metadataRepresentation.representationType);
}
let item: Item;
if (hasValue((this.object as any).dspaceObject)) {
const searchResult = this.object as ItemSearchResult;

View File

@@ -1 +1 @@
<ds-item-type-switcher [object]="object" [viewMode]="ElementViewMode.SetElement"></ds-item-type-switcher>
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import * as viewMode from '../../../shared/view-mode';
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { SetViewMode } from '../../view-mode';
import { VIEW_MODE_ELEMENT } from '../../../+item-page/simple/related-items/related-items-component';
@Component({
selector: 'ds-item-list-element',
@@ -18,5 +18,5 @@ import { SetViewMode } from '../../view-mode';
*/
@renderElementsFor(Item, SetViewMode.List)
export class ItemListElementComponent extends AbstractListableElementComponent<Item> {
ElementViewMode = viewMode.ElementViewMode;
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('JournalIssue', ElementViewMode.SetElement)
@rendersItemType('JournalIssue', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-issue-list-element',
styleUrls: ['./journal-issue-list-element.component.scss'],

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('JournalVolume', ElementViewMode.SetElement)
@rendersItemType('JournalVolume', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-volume-list-element',
styleUrls: ['./journal-volume-list-element.component.scss'],

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('Journal', ElementViewMode.SetElement)
@rendersItemType('Journal', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-journal-list-element',
styleUrls: ['./journal-list-element.component.scss'],

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('OrgUnit', ElementViewMode.SetElement)
@rendersItemType('OrgUnit', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-orgunit-list-element',
styleUrls: ['./orgunit-list-element.component.scss'],

View File

@@ -13,4 +13,3 @@
</ds-truncatable-part>
</span>
</ds-truncatable>

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('Person', ElementViewMode.SetElement)
@rendersItemType('Person', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-person-list-element',
styleUrls: ['./person-list-element.component.scss'],

View File

@@ -0,0 +1,15 @@
<ng-template #descTemplate>
<span class="text-muted">
<span *ngIf="item.filterMetadata(['person.identifier.jobtitle']).length > 0"
class="item-list-job-title">
<span *ngFor="let value of getValues(['person.identifier.jobtitle']); let last=last;">
<span [innerHTML]="value"><span [innerHTML]="value"></span></span>
</span>
</span>
</span>
</ng-template>
<ds-truncatable [id]="item.id">
<a [routerLink]="['/items/' + item.id]"
[innerHTML]="getFirstValue('dc.contributor.author')"
[tooltip]="descTemplate"></a>
</ds-truncatable>

View File

@@ -0,0 +1,16 @@
import { rendersItemType } from '../../../../items/item-type-decorator';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
import { Component } from '@angular/core';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { MetadataRepresentationType } from '../../../../../core/shared/metadata-representation/metadata-representation.model';
@rendersItemType('Person', VIEW_MODE_ELEMENT, MetadataRepresentationType.Item)
@Component({
selector: 'ds-person-metadata-list-element',
templateUrl: './person-metadata-list-element.component.html'
})
/**
* The component for displaying a list element for an item of the type Person
*/
export class PersonMetadataListElementComponent extends TypedItemSearchResultListElementComponent {
}

View File

@@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('Project', ElementViewMode.SetElement)
@rendersItemType('Project', VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-project-list-element',
styleUrls: ['./project-list-element.component.scss'],

View File

@@ -1,10 +1,10 @@
import { Component } from '@angular/core';
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../../items/item-type-decorator';
import { ElementViewMode } from '../../../../view-mode';
import { TypedItemSearchResultListElementComponent } from '../typed-item-search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType('Publication', ElementViewMode.SetElement)
@rendersItemType(DEFAULT_ITEM_TYPE, ElementViewMode.SetElement)
@rendersItemType('Publication', VIEW_MODE_ELEMENT)
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_ELEMENT)
@Component({
selector: 'ds-publication-list-element',
styleUrls: ['./publication-list-element.component.scss'],

View File

@@ -0,0 +1,2 @@
<ds-item-type-switcher [object]="metadataRepresentation" [viewMode]="viewMode">
</ds-item-type-switcher>

View File

@@ -0,0 +1,38 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ITEM } from '../../../items/switcher/item-type-switcher.component';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ItemMetadataListElementComponent } from './item-metadata-list-element.component';
import { By } from '@angular/platform-browser';
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
const mockItemMetadataRepresentation = new ItemMetadataRepresentation('type');
describe('ItemMetadataListElementComponent', () => {
let comp: ItemMetadataListElementComponent;
let fixture: ComponentFixture<ItemMetadataListElementComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [ItemMetadataListElementComponent],
providers: [
{ provide: ITEM, useValue: mockItemMetadataRepresentation }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ItemMetadataListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(ItemMetadataListElementComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
}));
it('should call an item-type-switcher component and pass the item-metadata-representation', () => {
const itemTypeSwitcher = fixture.debugElement.query(By.css('ds-item-type-switcher')).nativeElement;
expect(itemTypeSwitcher.object).toBe(mockItemMetadataRepresentation);
});
});

View File

@@ -0,0 +1,25 @@
import * as viewMode from '../../../view-mode';
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { Component } from '@angular/core';
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../items/item-type-decorator';
import { VIEW_MODE_METADATA } from '../../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
import { VIEW_MODE_ELEMENT } from '../../../../+item-page/simple/related-items/related-items-component';
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.Item)
@Component({
selector: 'ds-item-metadata-list-element',
templateUrl: './item-metadata-list-element.component.html'
})
/**
* A component for displaying MetadataRepresentation objects in the form of items
* It will send the MetadataRepresentation object along with ElementViewMode.SetElement to the ItemTypeSwitcherComponent,
* which will in his turn decide how to render the item as metadata.
*/
export class ItemMetadataListElementComponent extends MetadataRepresentationListElementComponent {
/**
* The view-mode we're currently on
* @type {ElementViewMode}
*/
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -0,0 +1,15 @@
import { Component, Inject } from '@angular/core';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
import { ITEM } from '../../items/switcher/item-type-switcher.component';
@Component({
selector: 'ds-metadata-representation-list-element',
template: ''
})
/**
* An abstract class for displaying a single MetadataRepresentation
*/
export class MetadataRepresentationListElementComponent {
constructor(@Inject(ITEM) public metadataRepresentation: MetadataRepresentation) {
}
}

View File

@@ -0,0 +1,3 @@
<div>
<span>{{metadataRepresentation.getPrimaryValue()}}</span>
</div>

View File

@@ -0,0 +1,39 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { PlainTextMetadataListElementComponent } from './plain-text-metadata-list-element.component';
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { ITEM } from '../../../items/switcher/item-type-switcher.component';
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type'), {
key: 'dc.contributor.author',
value: 'Test Author'
});
describe('PlainTextMetadataListElementComponent', () => {
let comp: PlainTextMetadataListElementComponent;
let fixture: ComponentFixture<PlainTextMetadataListElementComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [PlainTextMetadataListElementComponent],
providers: [
{ provide: ITEM, useValue: mockMetadataRepresentation }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PlainTextMetadataListElementComponent, {
set: {changeDetection: ChangeDetectionStrategy.Default}
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(PlainTextMetadataListElementComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
}));
it('should contain the value as plain text', () => {
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentation.value);
});
});

View File

@@ -0,0 +1,19 @@
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { Component } from '@angular/core';
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
import { DEFAULT_ITEM_TYPE, rendersItemType } from '../../../items/item-type-decorator';
import { VIEW_MODE_METADATA } from '../../../../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.PlainText)
// For now, authority controlled fields are rendered the same way as plain text fields
@rendersItemType(DEFAULT_ITEM_TYPE, VIEW_MODE_METADATA, MetadataRepresentationType.AuthorityControlled)
@Component({
selector: 'ds-plain-text-metadata-list-element',
templateUrl: './plain-text-metadata-list-element.component.html'
})
/**
* A component for displaying MetadataRepresentation objects in the form of plain text
* It will simply use the value retrieved from MetadataRepresentation.getPrimaryValue() to display as plain text
*/
export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent {
}

View File

@@ -1 +1 @@
<ds-item-type-switcher [object]="object" [viewMode]="ElementViewMode.SetElement"></ds-item-type-switcher>
<ds-item-type-switcher [object]="object" [viewMode]="viewMode"></ds-item-type-switcher>

View File

@@ -4,9 +4,9 @@ import { focusBackground } from '../../../animations/focus';
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import * as viewMode from '../../../../shared/view-mode';
import { SetViewMode } from '../../../view-mode';
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { VIEW_MODE_ELEMENT } from '../../../../+item-page/simple/related-items/related-items-component';
@Component({
selector: 'ds-item-search-result-list-element',
@@ -18,5 +18,5 @@ import { SearchResultListElementComponent } from '../search-result-list-element.
@renderElementsFor(ItemSearchResult, SetViewMode.List)
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
ElementViewMode = viewMode.ElementViewMode;
viewMode = VIEW_MODE_ELEMENT;
}

View File

@@ -94,6 +94,11 @@ import { CapitalizePipe } from './utils/capitalize.pipe';
import { ObjectKeysPipe } from './utils/object-keys-pipe';
import { MomentModule } from 'ngx-moment';
import { NouisliderModule } from 'ng2-nouislider';
import { PlainTextMetadataListElementComponent } from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
import { ItemMetadataListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component';
import { TooltipModule } from 'ngx-bootstrap';
import { PersonMetadataListElementComponent } from './object-list/item-list-element/item-types/person/person-metadata-list-element.component';
import { MetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/metadata-representation-list-element.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -117,6 +122,10 @@ const MODULES = [
TextMaskModule
];
const ROOT_MODULES = [
TooltipModule.forRoot()
];
const PIPES = [
// put shared pipes here
EnumKeysPipe,
@@ -184,12 +193,16 @@ const ENTRY_COMPONENTS = [
SearchResultGridElementComponent,
PublicationListElementComponent,
PersonListElementComponent,
PersonMetadataListElementComponent,
OrgUnitListElementComponent,
ProjectListElementComponent,
JournalListElementComponent,
JournalVolumeListElementComponent,
JournalIssueListElementComponent,
BrowseEntryListElementComponent
BrowseEntryListElementComponent,
PlainTextMetadataListElementComponent,
ItemMetadataListElementComponent,
MetadataRepresentationListElementComponent
];
const PROVIDERS = [
@@ -206,7 +219,8 @@ const DIRECTIVES = [
@NgModule({
imports: [
...MODULES
...MODULES,
...ROOT_MODULES
],
declarations: [
...PIPES,

View File

@@ -8,17 +8,7 @@ export enum SetViewMode {
Grid = 'grid'
}
/**
* Enum used for defining the view-mode of a single element
* Full Display the full element
* SetElement Display the element as part of a set
*/
export enum ElementViewMode {
Full,
SetElement
}
/**
* ViewMode refers to either a SetViewMode or ElementViewMode
*/
export type ViewMode = SetViewMode | ElementViewMode;
export type ViewMode = SetViewMode;