62849: fixed issues with multiple selections, improved UX, updating to use new REST response

This commit is contained in:
lotte
2019-07-29 15:42:30 +02:00
parent 16feb61ebf
commit 1f7b8b8210
20 changed files with 130 additions and 157 deletions

View File

@@ -431,7 +431,6 @@
"volume-title": "Volume Title", "volume-title": "Volume Title",
"publisher": "Publisher", "publisher": "Publisher",
"description": "Description" "description": "Description"
}, },
"listelement": { "listelement": {
"badge": "Publication" "badge": "Publication"
@@ -570,7 +569,7 @@
} }
}, },
"switch-configuration": { "switch-configuration": {
"title":"Show" "title": "Show"
}, },
"view-switch": { "view-switch": {
"show-list": "Show as list", "show-list": "Show as list",
@@ -1016,7 +1015,7 @@
"no-results": "No {{ type }} found" "no-results": "No {{ type }} found"
}, },
"submission": { "submission": {
"general":{ "general": {
"cannot_submit": "You have not the privilege to make a new submission.", "cannot_submit": "You have not the privilege to make a new submission.",
"deposit": "Deposit", "deposit": "Deposit",
"discard": { "discard": {
@@ -1038,10 +1037,8 @@
"title": "Edit Submission" "title": "Edit Submission"
}, },
"mydspace": { "mydspace": {
}, },
"sections": { "sections": {
"general": { "general": {
"add-more": "Add more", "add-more": "Add more",
"no-sections": "No options available", "no-sections": "No options available",
@@ -1066,7 +1063,6 @@
"submit.progressbar.license": "Deposit license", "submit.progressbar.license": "Deposit license",
"submit.progressbar.cclicense": "Creative commons license", "submit.progressbar.cclicense": "Creative commons license",
"submit.progressbar.detect-duplicate": "Potential duplicates", "submit.progressbar.detect-duplicate": "Potential duplicates",
"upload": { "upload": {
"no-entry": "No", "no-entry": "No",
"no-file-uploaded": "No file uploaded yet.", "no-file-uploaded": "No file uploaded yet.",
@@ -1097,6 +1093,21 @@
"info": "This operation can't be undone. Are you sure?" "info": "This operation can't be undone. Are you sure?"
} }
} }
},
"describe": {
"relationship-lookup": {
"search": "Go",
"loading": "Loading...",
"title": "Select a {{ label }}",
"toggle-dropdown": "Toggle dropdown",
"select-page": "Select page",
"deselect-page": "Deselect page",
"select-all": "Select all",
"deselect-all": "Deselect all",
"close": "Close",
"selected": "Selected {{ size }} items",
"placeholder": "Search query"
}
} }
}, },
"workflow": { "workflow": {
@@ -1132,7 +1143,6 @@
"reject_help": "If you have reviewed the item and found it is <strong>not</strong> suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", "reject_help": "If you have reviewed the item and found it is <strong>not</strong> suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.",
"return": "Return to pool", "return": "Return to pool",
"return_help": "Return the task to the pool so that another user may perform the task." "return_help": "Return the task to the pool so that another user may perform the task."
}, },
"pool": { "pool": {
"claim": "Claim", "claim": "Claim",

View File

@@ -8,39 +8,24 @@
"input": { "input": {
"type": "lookup-relation" "type": "lookup-relation"
}, },
"label": "Journal Issue", "label": "Author",
"mandatory": true, "mandatory": true,
"repeatable": true, "repeatable": true,
"mandatoryMessage": "Required field!", "mandatoryMessage": "At least one author (plain text or relationship) is required",
"hints": "Select a journal issue for this publication.", "hints": "Add an author",
"selectableMetadata": [ "selectableRelationships": [
{ {
"metadata": "relation.isPublicationOfJournalIssue", "relationshipType": "isAuthorOfPublication",
"label": null, "filter": null,
"authority": null, "searchConfiguration": "personConfiguration"
"closed": null
} }
], ],
"languageCodes": []
}
]
},
{
"fields": [
{
"input": {
"type": "name"
},
"label": "Authors",
"mandatory": false,
"repeatable": true,
"hints": "Enter the names of the authors of this item.",
"selectableMetadata": [ "selectableMetadata": [
{ {
"metadata": "dc.contributor.author", "metadata": "dc.contributor.author",
"label": null, "label": null,
"authority": null, "authority": null,
"closed": null "closed": false
} }
], ],
"languageCodes": [] "languageCodes": []

View File

@@ -206,7 +206,7 @@ export class SearchConfigurationService implements OnDestroy {
* @returns {Observable<string>} Emits the current fixed filter as a string * @returns {Observable<string>} Emits the current fixed filter as a string
*/ */
getCurrentFixedFilter(): Observable<string> { getCurrentFixedFilter(): Observable<string> {
return this.routeService.getRouteParameterValue('fixedFilterQuery').pipe(tap((t) => console.log(t))); return this.routeService.getRouteParameterValue('fixedFilterQuery');
} }
/** /**

View File

@@ -1,8 +1,26 @@
import { isEmpty } from '../../shared/empty.util'; import { isEmpty } from '../../shared/empty.util';
import { GenericConstructor } from '../shared/generic-constructor';
import { EquatableObject } from './equatable';
const excludedFromEquals = new Map(); const excludedFromEquals = new Map();
const fieldsForEqualsMap = new Map(); const fieldsForEqualsMap = new Map();
export function inheritEquatable(parentCo: GenericConstructor<EquatableObject<any>>) {
return function decorator(childCo: GenericConstructor<EquatableObject<any>>) {
const parentExcludedFields = getExcludedFromEqualsFor(parentCo) || [];
const excludedFields = getExcludedFromEqualsFor(childCo) || [];
excludedFromEquals.set(childCo, [...excludedFields, ...parentExcludedFields]);
const mappedFields = fieldsForEqualsMap.get(childCo) || new Map();
const parentMappedFields = fieldsForEqualsMap.get(parentCo) || new Map();
Array.from(parentMappedFields.keys())
.filter((key) => !Array.from(mappedFields.keys()).includes(key))
.forEach((key) => {
fieldsForEquals(...parentMappedFields.get(key))(new childCo(), key);
});
}
}
export function excludeFromEquals(object: any, propertyName: string): any { export function excludeFromEquals(object: any, propertyName: string): any {
if (!object) { if (!object) {
return; return;
@@ -14,7 +32,7 @@ export function excludeFromEquals(object: any, propertyName: string): any {
excludedFromEquals.set(object.constructor, [...list, propertyName]); excludedFromEquals.set(object.constructor, [...list, propertyName]);
} }
export function getExcludedFromEqualsFor(constructor: Function) { export function getExcludedFromEqualsFor(constructor: Function): string[] {
return excludedFromEquals.get(constructor) || []; return excludedFromEquals.get(constructor) || [];
} }
@@ -33,7 +51,7 @@ export function fieldsForEquals(...fields: string[]): any {
} }
export function getFieldsForEquals(constructor: Function, field: string) { export function getFieldsForEquals(constructor: Function, field: string): string[] {
const fieldMap = fieldsForEqualsMap.get(constructor) || new Map(); const fieldMap = fieldsForEqualsMap.get(constructor) || new Map();
return fieldMap.get(field); return fieldMap.get(field);
} }

View File

@@ -1,5 +1,5 @@
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title" id="modal-title">Choose a {{fieldName}}</h4> <h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relationship-lookup.title' | translate:{label: label}) }}</h4>
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title" <button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
(click)="modal.dismiss()"> (click)="modal.dismiss()">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
@@ -13,10 +13,10 @@
<div class="col-8"> <div class="col-8">
<form class="input-group mb-3" #queryForm="ngForm" <form class="input-group mb-3" #queryForm="ngForm"
(ngSubmit)="search(queryForm.value.query)"> (ngSubmit)="search(queryForm.value.query)">
<input type="text" class="form-control" name="query" placeholder="Search query" <input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.placeholder' | translate"
[ngModel]="searchQuery"> [ngModel]="searchQuery">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">Go</button> <button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search' | translate) }}</button>
</div> </div>
</form> </form>
<div *ngIf="repeatable" class="position-absolute"> <div *ngIf="repeatable" class="position-absolute">
@@ -46,20 +46,20 @@
class="btn btn-outline-secondary rounded-right"> class="btn btn-outline-secondary rounded-right">
<span class="spinner-border spinner-border-sm" role="status" <span class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span> aria-hidden="true"></span>
<span class="sr-only">Loading...</span> <span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.loading' | translate) }}</span>
</button> </button>
<button *ngIf="!selectAllLoading" id="resultdropdown" type="button" <button *ngIf="!selectAllLoading" id="resultdropdown" type="button"
ngbDropdownToggle ngbDropdownToggle
class="btn btn-outline-secondary dropdown-toggle-split" class="btn btn-outline-secondary dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"> aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span> <span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.toggle-dropdown' | translate) }}</span>
</button> </button>
<div ngbDropdownMenu aria-labelledby="resultdropdown"> <div ngbDropdownMenu aria-labelledby="resultdropdown">
<button class="dropdown-item" (click)="selectPage(resultsRD?.payload?.page)">Select page</button> <button class="dropdown-item" (click)="selectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.select-page' | translate) }}</button>
<button class="dropdown-item" (click)="deselectPage(resultsRD?.payload?.page)">Deselect page</button> <button class="dropdown-item" (click)="deselectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.deselect-page' | translate) }}</button>
<button class="dropdown-item" (click)="selectAll()">Select all</button> <button class="dropdown-item" (click)="selectAll()">{{ ('submission.sections.describe.relationship-lookup.select-all' | translate) }}</button>
<button class="dropdown-item" (click)="deselectAll()">Deselect all</button> <button class="dropdown-item" (click)="deselectAll()">{{ ('submission.sections.describe.relationship-lookup.deselect-all' | translate) }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -68,15 +68,16 @@
[sortConfig]="this.searchConfig?.sort" [sortConfig]="this.searchConfig?.sort"
[searchConfig]="this.searchConfig" [searchConfig]="this.searchConfig"
[selectable]="true" [selectable]="true"
[selectionConfig]="{ repeatable: repeatable, listId: listId }"> [selectionConfig]="{ repeatable: repeatable, listId: listId }"
(deselectObject)="allSelected = false"
>
</ds-search-results> </ds-search-results>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<small>Selected {{(selection$ | async)?.length || 0}} items</small> <small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate:{size: (selection$ | async)?.length || 0}) }}</small>
<div> <div>
<button type="button" class="btn btn-outline-secondary" (click)="modal.dismiss()">Cancel</button> <button type="button" class="btn btn-danger" (click)="close()">{{ ('submission.sections.describe.relationship-lookup.close' | translate) }}</button>
<button type="button" class="btn btn-danger" (click)="close()">Ok</button>
</div> </div>
</div> </div>

View File

@@ -35,7 +35,9 @@ const RELATION_TYPE_FILTER_PREFIX = 'f.entityType=';
}) })
export class DsDynamicLookupRelationModalComponent implements OnInit { export class DsDynamicLookupRelationModalComponent implements OnInit {
relationKey: string; relationKey: string;
fieldName: string; label: string;
filter: string;
searchConfiguration: string;
listId: string; listId: string;
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>; resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
searchConfig: PaginatedSearchOptions; searchConfig: PaginatedSearchOptions;
@@ -49,21 +51,20 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
pageSize: 10 pageSize: 10
}); });
selection$: Observable<ListableObject[]>; selection$: Observable<ListableObject[]>;
fixedFilter: string;
constructor(public modal: NgbActiveModal, private searchService: SearchService, private router: Router, private selectableListService: SelectableListService, private searchConfigService: SearchConfigurationService, private routeService: RouteService) { constructor(public modal: NgbActiveModal, private searchService: SearchService, private router: Router, private selectableListService: SelectableListService, private searchConfigService: SearchConfigurationService, private routeService: RouteService) {
} }
ngOnInit(): void { ngOnInit(): void {
this.resetRoute(); this.resetRoute();
this.fixedFilter = RELATION_TYPE_FILTER_PREFIX + this.fieldName; this.routeService.setParameter('fixedFilterQuery', this.filter);
this.routeService.setParameter('fixedFilterQuery', this.fixedFilter); this.routeService.setParameter('configuration', this.searchConfiguration);
this.selection$ = this.selectableListService.getSelectableList(this.listId).pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : [])); this.selection$ = this.selectableListService.getSelectableList(this.listId).pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection))); this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe( this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
map((options) => { map((options) => {
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: RELATION_TYPE_FILTER_PREFIX + this.fieldName }) return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.filter, configuration: this.searchConfiguration })
}), }),
switchMap((options) => { switchMap((options) => {
this.searchConfig = options; this.searchConfig = options;
@@ -85,9 +86,9 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
} }
search(query: string) { search(query: string) {
this.allSelected = false;
this.searchQuery = query; this.searchQuery = query;
this.resetRoute(); this.resetRoute();
this.selectableListService.deselectAll(this.listId);
} }
close() { close() {
@@ -100,7 +101,6 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
}); });
} }
selectPage(page: SearchResult<DSpaceObject>[]) { selectPage(page: SearchResult<DSpaceObject>[]) {
this.selectableListService.select(this.listId, page); this.selectableListService.select(this.listId, page);
} }

View File

@@ -1,4 +1,3 @@
<script src="dynamic-lookup-relation.component.ts"></script>
<div> <div>
<div *ngIf="model.repeatable || !((model.value | async) && (model.value | async).length > 0)" class="form-row align-items-center"> <div *ngIf="model.repeatable || !((model.value | async) && (model.value | async).length > 0)" class="form-row align-items-center">
<div class="col"> <div class="col">
@@ -12,29 +11,19 @@
</div> </div>
<div class="col-auto text-center"> <div class="col-auto text-center">
<button class="btn btn-secondary" <button class="btn btn-secondary"
type="button" type="submit"
ngbTooltip="{{'form.lookup-help' | translate}}" ngbTooltip="{{'form.lookup-help' | translate}}"
placement="top" placement="top"
(click)="openLookup(); $event.stopPropagation();">{{'form.lookup' | translate}} (click)="openLookup(); $event.stopPropagation();">{{'form.lookup' | translate}}
</button> </button>
</div> </div>
<!--<div class="col-auto text-center">-->
<!--<button class="btn btn-secondary"-->
<!--type="button"-->
<!--ngbTooltip="{{'form.add-help' | translate}}"-->
<!--#tooltip = ngbTooltip-->
<!--placement="top"-->
<!--[disabled]="!hasResultsSelected()"-->
<!--(click)="add(); tooltip.close(); $event.stopPropagation();">{{'form.add' | translate}}-->
<!--</button>-->
<!--</div>-->
</div> </div>
<div class="mt-3"> <div class="mt-3">
<ul class="list-unstyled"> <ul class="list-unstyled">
<li *ngFor="let result of (model.value | async)"> <li *ngFor="let result of (model.value | async)">
<ng-container *ngVar="result.indexableObject as value"> <ng-container *ngVar="result.indexableObject as value">
<button type="button" class="close float-left" aria-label="Close button" <button type="button" class="close float-left" aria-label="Close button"
(click)="removeSelection(value)"> (click)="removeSelection(result)">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
<span class="d-inline-block align-middle ml-1">{{value.name}}</span> <span class="d-inline-block align-middle ml-1">{{value.name}}</span>

View File

@@ -1,10 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { import { DynamicFormControlComponent, DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
DynamicFormControlComponent,
DynamicFormLayoutService,
DynamicFormValidationService
} from '@ng-dynamic-forms/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@@ -12,11 +8,9 @@ import { DsDynamicLookupRelationModalComponent } from './dynamic-lookup-relation
import { DynamicLookupRelationModel } from './dynamic-lookup-relation.model'; import { DynamicLookupRelationModel } from './dynamic-lookup-relation.model';
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { RelationshipService } from '../../../../../../core/data/relationship.service'; import { RelationshipService } from '../../../../../../core/data/relationship.service';
import { Item } from '../../../../../../core/shared/item.model';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer'; import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { SearchResult } from '../../../../../search/search-result.model'; import { SearchResult } from '../../../../../search/search-result.model';
@@ -39,8 +33,10 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
modalRef: NgbModalRef; modalRef: NgbModalRef;
modalValuesString = ''; modalValuesString = '';
fieldName: string;
listId: string; listId: string;
filter: string;
searchConfig: string;
constructor(private modalService: NgbModal, constructor(private modalService: NgbModal,
protected layoutService: DynamicFormLayoutService, protected layoutService: DynamicFormLayoutService,
@@ -52,8 +48,9 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
} }
ngOnInit(): void { ngOnInit(): void {
this.fieldName = this.model.name.substring(RELATION_TYPE_METADATA_PREFIX.length); this.filter = this.model.relationship.filter;
this.listId = 'list-' + this.fieldName; this.searchConfig = this.model.relationship.searchConfiguration;
this.listId = 'list-' + this.model.relationship.relationshipType;
this.model.value = this.selectableListService.getSelectableList(this.listId).pipe( this.model.value = this.selectableListService.getSelectableList(this.listId).pipe(
map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []), map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []),
); );
@@ -69,27 +66,15 @@ export class DsDynamicLookupRelationComponent extends DynamicFormControlComponen
modalComp.repeatable = this.model.repeatable; modalComp.repeatable = this.model.repeatable;
modalComp.relationKey = this.model.name; modalComp.relationKey = this.model.name;
modalComp.listId = this.listId; modalComp.listId = this.listId;
modalComp.fieldName = this.fieldName; modalComp.filter = this.filter;
modalComp.fieldName = this.searchConfig;
modalComp.label = this.model.label;
this.modalRef.result.then((resultString = '') => { this.modalRef.result.then((resultString = '') => {
this.modalValuesString = resultString; this.modalValuesString = resultString;
}); });
} }
// add() {
// if (isNotEmpty(this.model.value)) {
// this.model.value = [...this.model.value, ...this.selectedResults];
// } else {
// this.model.value = this.selectedResults;
// }
//
// this.modalValuesString = '';
// this.selectedResults = [];
// this.selectedResults.forEach((item: Item) => {
// this.relationService.addRelationship(this.model.item, item);
// })
// }
removeSelection(object: SearchResult<DSpaceObject>) { removeSelection(object: SearchResult<DSpaceObject>) {
this.selectableListService.deselectSingle(this.listId, object); this.selectableListService.deselectSingle(this.listId, object);
} }

View File

@@ -1,6 +1,7 @@
import { DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core'; import { DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core';
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-input.model'; import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-input.model';
import { Item } from '../../../../../../core/shared/item.model'; import { Item } from '../../../../../../core/shared/item.model';
import { RelationshipOptions } from './model/relationship-options.model';
export const DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION = 'LOOKUP_RELATION'; export const DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION = 'LOOKUP_RELATION';
@@ -8,6 +9,7 @@ export interface DynamicLookupRelationModelConfig extends DsDynamicInputModelCon
value?: any; value?: any;
repeatable: boolean; repeatable: boolean;
item: Item; item: Item;
relationship: RelationshipOptions;
} }
export class DynamicLookupRelationModel extends DsDynamicInputModel { export class DynamicLookupRelationModel extends DsDynamicInputModel {
@@ -15,6 +17,7 @@ export class DynamicLookupRelationModel extends DsDynamicInputModel {
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION; @serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_RELATION;
@serializable() value: any; @serializable() value: any;
@serializable() repeatable: boolean; @serializable() repeatable: boolean;
relationship: RelationshipOptions;
item: Item; item: Item;
constructor(config: DynamicLookupRelationModelConfig, layout?: DynamicFormControlLayout) { constructor(config: DynamicLookupRelationModelConfig, layout?: DynamicFormControlLayout) {
@@ -25,6 +28,7 @@ export class DynamicLookupRelationModel extends DsDynamicInputModel {
this.disabled = true; this.disabled = true;
this.repeatable = config.repeatable; this.repeatable = config.repeatable;
this.item = config.item; this.item = config.item;
this.relationship = config.relationship;
this.valueUpdates.next(config.value); this.valueUpdates.next(config.value);
} }
} }

View File

@@ -0,0 +1,5 @@
export interface RelationshipOptions {
relationshipType: string;
filter: string;
searchConfiguration: string;
}

View File

@@ -2,6 +2,7 @@ import { autoserialize } from 'cerialize';
import { FormRowModel } from '../../../../core/config/models/config-submission-forms.model'; import { FormRowModel } from '../../../../core/config/models/config-submission-forms.model';
import { LanguageCode } from './form-field-language-value.model'; import { LanguageCode } from './form-field-language-value.model';
import { FormFieldMetadataValueObject } from './form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from './form-field-metadata-value.model';
import { RelationshipOptions } from '../ds-dynamic-form-ui/models/lookup-relation/model/relationship-options.model';
export class FormFieldModel { export class FormFieldModel {
@@ -32,6 +33,9 @@ export class FormFieldModel {
@autoserialize @autoserialize
selectableMetadata: FormFieldMetadataValueObject[]; selectableMetadata: FormFieldMetadataValueObject[];
@autoserialize
selectableRelationships: RelationshipOptions[];
@autoserialize @autoserialize
rows: FormRowModel[]; rows: FormRowModel[];

View File

@@ -1,20 +1,14 @@
import { FieldParser } from './field-parser'; import { FieldParser } from './field-parser';
import {
DynamicLookupModel,
DynamicLookupModelConfig
} from '../ds-dynamic-form-ui/models/lookup/dynamic-lookup.model';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model'; import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
import { import { DynamicLookupRelationModel, DynamicLookupRelationModelConfig } from '../ds-dynamic-form-ui/models/lookup-relation/dynamic-lookup-relation.model';
DynamicLookupRelationModel,
DynamicLookupRelationModelConfig
} from '../ds-dynamic-form-ui/models/lookup-relation/dynamic-lookup-relation.model';
export class LookupRelationFieldParser extends FieldParser { export class LookupRelationFieldParser extends FieldParser {
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any { public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
const lookupModelConfig: DynamicLookupRelationModelConfig = this.initModel(null, label); const lookupModelConfig: DynamicLookupRelationModelConfig = this.initModel(null, label);
lookupModelConfig.repeatable = this.configData.repeatable; lookupModelConfig.repeatable = this.configData.repeatable;
/* TODO what to do with multiple relationships? */
lookupModelConfig.relationship = this.configData.selectableRelationships[0];
return new DynamicLookupRelationModel(lookupModelConfig); return new DynamicLookupRelationModel(lookupModelConfig);
} }
} }

View File

@@ -7,6 +7,8 @@
(pageChange)="onPageChange($event)" (pageChange)="onPageChange($event)"
(pageSizeChange)="onPageSizeChange($event)" (pageSizeChange)="onPageSizeChange($event)"
(sortDirectionChange)="onSortDirectionChange($event)" (sortDirectionChange)="onSortDirectionChange($event)"
(deselectObject)="deselectObject.emit($event)"
(selectObject)="selectObject.emit($event)"
(sortFieldChange)="onSortFieldChange($event)" (sortFieldChange)="onSortFieldChange($event)"
[selectable]="selectable" [selectable]="selectable"
[selectionConfig]="selectionConfig" [selectionConfig]="selectionConfig"

View File

@@ -35,6 +35,8 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
@Input() hideGear = false; @Input() hideGear = false;
@Input() selectable = false; @Input() selectable = false;
@Input() selectionConfig: {repeatable: boolean, listId: string}; @Input() selectionConfig: {repeatable: boolean, listId: string};
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
pageInfo: Observable<PageInfo>; pageInfo: Observable<PageInfo>;
private sub; private sub;

View File

@@ -1,7 +1,9 @@
import { SearchResult } from '../../search/search-result.model'; import { SearchResult } from '../../search/search-result.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { searchResultFor } from '../../search/search-result-element-decorator'; import { searchResultFor } from '../../search/search-result-element-decorator';
import { inheritEquatable } from '../../../core/utilities/equals.decorators';
@searchResultFor(Item) @searchResultFor(Item)
@inheritEquatable(SearchResult)
export class ItemSearchResult extends SearchResult<Item> { export class ItemSearchResult extends SearchResult<Item> {
} }

View File

@@ -79,8 +79,11 @@ export class ObjectListComponent {
*/ */
@Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>(); @Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>();
@Output() paginationChange: EventEmitter<SortDirection> = new EventEmitter<any>(); @Output() paginationChange: EventEmitter<any> = new EventEmitter<any>();
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
/** /**
* An event fired when the sort field is changed. * An event fired when the sort field is changed.
* Event's payload equals to the newly selected sort field. * Event's payload equals to the newly selected sort field.
@@ -108,66 +111,24 @@ export class ObjectListComponent {
this.paginationChange.emit(event); this.paginationChange.emit(event);
} }
// isDisabled(object: ListableObject): boolean {
// return hasValue(this.previousSelection.find((selected) => selected === object));
// }
selectCheckbox(value: boolean, object: ListableObject) { selectCheckbox(value: boolean, object: ListableObject) {
if (value) { if (value) {
this.selectionService.selectSingle(this.selectionConfig.listId, object); this.selectionService.selectSingle(this.selectionConfig.listId, object);
this.selectObject.emit(object);
} else { } else {
this.selectionService.deselectSingle(this.selectionConfig.listId, object); this.selectionService.deselectSingle(this.selectionConfig.listId, object);
this.deselectObject.emit(object);
} }
} }
selectRadio(value: boolean, object: ListableObject) { selectRadio(value: boolean, object: ListableObject) {
if (value) { if (value) {
this.selectionService.selectSingle(this.selectionConfig.listId, object, false); this.selectionService.selectSingle(this.selectionConfig.listId, object, false);
this.selectObject.emit(object);
} else {
this.selectionService.deselectSingle(this.selectionConfig.listId, object);
this.deselectObject.emit(object);
} }
} }
selectPage(page: SearchResult<DSpaceObject>[]) {
this.selectionService.select(this.selectionConfig.listId, this.objects.payload.page);
}
deselectPage(page: SearchResult<DSpaceObject>[]) {
this.selectionService.deselect(this.selectionConfig.listId, this.objects.payload.page);
}
deselectAll() {
this.selectionService.deselectAll(this.selectionConfig.listId);
}
// isAllSelected() {
// return this.allSelected;
// }
//
// isSomeSelected() {
// return isNotEmpty(this.selection);
// }
//
//
// selectAll() {
// this.allSelected = true;
// this.selectAllLoading = true;
// const fullPagination = Object.assign(new PaginationComponentOptions(), {
// query: this.searchQuery,
// currentPage: 1,
// pageSize: Number.POSITIVE_INFINITY
// });
// const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
// const results = this.searchService.search(fullSearchConfig);
// results.pipe(
// getSucceededRemoteData(),
// map((resultsRD) => resultsRD.payload.page),
// tap(() => this.selectAllLoading = false)
// )
// .subscribe((results) =>
// this.selection = results
// .map((searchResult) => searchResult.indexableObject)
// .filter((dso) => hasNoValue(this.previousSelection.find((object) => object === dso)))
// );
// }
} }

View File

@@ -71,10 +71,12 @@ function select(state: SelectableListState, action: SelectableListSelectAction)
function selectSingle(state: SelectableListState, action: SelectableListSelectSingleAction) { function selectSingle(state: SelectableListState, action: SelectableListSelectSingleAction) {
let newSelection; let newSelection;
if (action.payload.multipleSelectionsAllowed && !isObjectInSelection(state.selection, action.payload.object)) { if (!isObjectInSelection(state.selection, action.payload.object)) {
newSelection = [...state.selection, action.payload.object]; if (action.payload.multipleSelectionsAllowed) {
} else { newSelection = [...state.selection, action.payload.object];
newSelection = [action.payload.object]; } else {
newSelection = [action.payload.object];
}
} }
return Object.assign({}, state, { selection: newSelection }); return Object.assign({}, state, { selection: newSelection });
} }

View File

@@ -37,8 +37,9 @@ export class SelectableListService {
* Select an object in a specific list in the store * Select an object in a specific list in the store
* @param {string} id The id of the list on which the object should be selected * @param {string} id The id of the list on which the object should be selected
* @param {ListableObject} object The object to select * @param {ListableObject} object The object to select
* @param {boolean} multipleSelectionsAllowed Defines if the multiple selections are allowed for this selectable list
*/ */
selectSingle(id: string, object: ListableObject, multipleSelectionsAllowed?) { selectSingle(id: string, object: ListableObject, multipleSelectionsAllowed?: boolean) {
this.store.dispatch(new SelectableListSelectSingleAction(id, object, multipleSelectionsAllowed)); this.store.dispatch(new SelectableListSelectSingleAction(id, object, multipleSelectionsAllowed));
} }
@@ -86,7 +87,7 @@ export class SelectableListService {
isObjectSelected(id: string, object: ListableObject): Observable<boolean> { isObjectSelected(id: string, object: ListableObject): Observable<boolean> {
return this.getSelectableList(id).pipe( return this.getSelectableList(id).pipe(
filter((state: SelectableListState) => hasValue(state)), filter((state: SelectableListState) => hasValue(state)),
map((state: SelectableListState) => isNotEmpty(state.selection) && hasValue(state.selection.find((selected) => selected === object))), map((state: SelectableListState) => isNotEmpty(state.selection) && hasValue(state.selection.find((selected) => selected.equals(object)))),
startWith(false), startWith(false),
distinctUntilChanged() distinctUntilChanged()
); );

View File

@@ -7,6 +7,8 @@
[hideGear]="true" [hideGear]="true"
[selectable]="selectable" [selectable]="selectable"
[selectionConfig]="selectionConfig" [selectionConfig]="selectionConfig"
(deselectObject)="deselectObject.emit($event)"
(selectObject)="selectObject.emit($event)"
> >
</ds-viewable-collection> </ds-viewable-collection>
</div> </div>

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { fadeIn, fadeInOut } from '../../animations/fade'; import { fadeIn, fadeInOut } from '../../animations/fade';
@@ -8,6 +8,7 @@ import { SearchResult } from '../search-result.model';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list';
import { hasNoValue, isNotEmpty } from '../../empty.util'; import { hasNoValue, isNotEmpty } from '../../empty.util';
import { SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortOptions } from '../../../core/cache/models/sort-options.model';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
@Component({ @Component({
selector: 'ds-search-results', selector: 'ds-search-results',
@@ -58,6 +59,11 @@ export class SearchResultsComponent {
@Input() selectable = false; @Input() selectable = false;
@Input() selectionConfig: {repeatable: boolean, listId: string}; @Input() selectionConfig: {repeatable: boolean, listId: string};
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
/** /**
* Method to change the given string by surrounding it by quotes if not already present. * Method to change the given string by surrounding it by quotes if not already present.
*/ */