diff --git a/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts b/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts index 73e02ca29d..12a87c2277 100644 --- a/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts +++ b/src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts @@ -1,11 +1,14 @@ import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; -import { distinctUntilChanged, flatMap, map, switchMap } from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, switchMap, tap } from 'rxjs/operators'; import { PaginatedList } from '../../../../core/data/paginated-list'; import { RemoteData } from '../../../../core/data/remote-data'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Item } from '../../../../core/shared/item.model'; -import { getFinishedRemoteData, getSucceededRemoteData } from '../../../../core/shared/operators'; +import { + getFirstSucceededRemoteDataPayload, + getSucceededRemoteData +} from '../../../../core/shared/operators'; import { hasValue } from '../../../../shared/empty.util'; /** @@ -75,16 +78,19 @@ export const paginatedRelationsToItems = (thisId: string) => getSucceededRemoteData(), switchMap((relationshipsRD: RemoteData>) => { return observableCombineLatest( - ...relationshipsRD.payload.page.map((rel: Relationship) => observableCombineLatest(rel.leftItem.pipe(getFinishedRemoteData()), rel.rightItem.pipe(getFinishedRemoteData()))) - ).pipe( + relationshipsRD.payload.page.map((rel: Relationship) => + observableCombineLatest([ + rel.leftItem.pipe(getFirstSucceededRemoteDataPayload()), + rel.rightItem.pipe(getFirstSucceededRemoteDataPayload())] + ) + )).pipe( map((arr) => arr - .filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded) .map(([leftItem, rightItem]) => { - if (leftItem.payload.id === thisId) { - return rightItem.payload; - } else if (rightItem.payload.id === thisId) { - return leftItem.payload; + if (leftItem.id === thisId) { + return rightItem; + } else if (rightItem.id === thisId) { + return leftItem; } }) .filter((item: Item) => hasValue(item)) diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 4000883753..68be319027 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, race as observableRace } from 'rxjs'; -import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, startWith, switchMap } from 'rxjs/operators'; import { hasValue, hasValueOperator, @@ -97,7 +97,8 @@ export class RemoteDataBuildService { let isSuccessful: boolean; let error: RemoteDataError; if (hasValue(reqEntry) && hasValue(reqEntry.response)) { - isSuccessful = reqEntry.response.isSuccessful; + isSuccessful = reqEntry.response.statusCode === 204 || + reqEntry.response.statusCode >= 200 && reqEntry.response.statusCode < 300 && hasValue(payload); const errorMessage = isSuccessful === false ? (reqEntry.response as ErrorResponse).errorMessage : undefined; if (hasValue(errorMessage)) { error = new RemoteDataError( @@ -140,6 +141,7 @@ export class RemoteDataBuildService { ); })); }), + startWith([]), distinctUntilChanged(), ); const pageInfo$ = requestEntry$.pipe( @@ -156,7 +158,7 @@ export class RemoteDataBuildService { }) ); - const payload$ = observableCombineLatest(tDomainList$, pageInfo$).pipe( + const payload$ = observableCombineLatest([tDomainList$, pageInfo$]).pipe( map(([tDomainList, pageInfo]) => { return new PaginatedList(pageInfo, tDomainList); }) diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index eab3c7052b..9abb8edb3c 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -32,7 +32,7 @@ import { Relationship } from '../shared/item-relationships/relationship.model'; import { RELATIONSHIP } from '../shared/item-relationships/relationship.resource-type'; import { Item } from '../shared/item.model'; import { - configureRequest, + configureRequest, getFirstSucceededRemoteDataPayload, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData @@ -323,8 +323,7 @@ export class RelationshipService extends DataService { private isItemMatchWithItemRD(itemRD$: Observable>, itemCheck: Item): Observable { return itemRD$.pipe( - getSucceededRemoteData(), - map((itemRD: RemoteData) => itemRD.payload), + getFirstSucceededRemoteDataPayload(), map((item: Item) => item.uuid === itemCheck.uuid) ); } @@ -378,6 +377,7 @@ export class RelationshipService extends DataService { let count = 0 const update$: Observable> = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel) .pipe( + filter((relation: Relationship) => hasValue(relation)), switchMap((relation: Relationship) => relation.relationshipType.pipe( getSucceededRemoteData(), diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 14d101a448..f0fc69aa13 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -1,6 +1,6 @@ import { Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { filter, find, flatMap, map, tap } from 'rxjs/operators'; +import { filter, find, flatMap, map, take, tap } from 'rxjs/operators'; import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { SearchResult } from '../../shared/search/search-result.model'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; @@ -65,7 +65,7 @@ export const getPaginatedListPayload = () => export const getSucceededRemoteData = () => (source: Observable>): Observable> => - source.pipe(find((rd: RemoteData) => rd.hasSucceeded)); + source.pipe(filter((rd: RemoteData) => rd.hasSucceeded), take(1)); /** * Get the first successful remotely retrieved object diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index 3529736820..4dd0951d70 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -7,10 +7,10 @@ [ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"> -
+
-
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index b60307de81..662ba02f93 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -33,7 +33,7 @@ import { DynamicDatePickerModel, DynamicFormComponentService, DynamicFormControl, DynamicFormControlContainerComponent, - DynamicFormControlEvent, + DynamicFormControlEvent, DynamicFormControlEventType, DynamicFormControlModel, DynamicFormLayout, DynamicFormLayoutService, DynamicFormRelationService, @@ -60,7 +60,7 @@ import { DYNAMIC_FORM_CONTROL_TYPE_DSDATEPICKER } from './models/date-picker/dat import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP } from './models/lookup/dynamic-lookup.model'; import { DynamicListCheckboxGroupModel } from './models/list/dynamic-list-checkbox-group.model'; import { DynamicListRadioGroupModel } from './models/list/dynamic-list-radio-group.model'; -import { hasNoValue, hasValue, isNotEmpty, isNotUndefined } from '../../../empty.util'; +import { hasNoValue, hasValue, isEmpty, isNotEmpty, isNotUndefined } from '../../../empty.util'; import { DYNAMIC_FORM_CONTROL_TYPE_LOOKUP_NAME } from './models/lookup/dynamic-lookup-name.model'; import { DsDynamicTagComponent } from './models/tag/dynamic-tag.component'; import { DsDatePickerComponent } from './models/date-picker/date-picker.component'; @@ -73,7 +73,7 @@ import { DsDynamicFormArrayComponent } from './models/array-group/dynamic-form-a import { DsDynamicRelationGroupComponent } from './models/relation-group/dynamic-relation-group.components'; import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './models/relation-group/dynamic-relation-group.model'; import { DsDatePickerInlineComponent } from './models/date-picker-inline/dynamic-date-picker-inline.component'; -import { map, startWith, switchMap, find, take } from 'rxjs/operators'; +import { map, startWith, switchMap, find, take, tap } from 'rxjs/operators'; import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; import { SearchResult } from '../../../search/search-result.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; @@ -246,7 +246,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo .pipe( getAllSucceededRemoteData(), getRemoteDataPayload()); - this.relationshipValue$ = observableCombineLatest(this.item$.pipe(take(1)), relationship$).pipe( + this.relationshipValue$ = observableCombineLatest([this.item$.pipe(take(1)), relationship$]).pipe( switchMap(([item, relationship]: [Item, Relationship]) => relationship.leftItem.pipe( getSucceededRemoteData(), @@ -278,7 +278,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo } ngOnChanges(changes: SimpleChanges) { - if (changes) { + if (changes && !this.isRelationship) { super.ngOnChanges(changes); if (this.model && this.model.placeholder) { this.model.placeholder = this.translateService.instant(this.model.placeholder); @@ -322,7 +322,6 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo * Open a modal where the user can select relationships to be added to item being submitted */ openLookup() { - this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, { size: 'lg' }); @@ -330,11 +329,14 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo modalComp.query = this.model.value ? this.model.value.value : ''; if (hasValue(this.model.value)) { - // this.model.reset(); - // this.model.value = undefined; - // this.model.valueUpdates(undefined); - this.model.value.value = undefined; - this.change.emit(); + this.model.value = ''; + this.onChange({ + $event: { previousIndex: 0 }, + context: { index: 0 }, + control: this.control, + model: this.model, + type: DynamicFormControlEventType.Change + }); } this.submissionService.dispatchSave(this.model.submissionId); @@ -368,5 +370,6 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo this.subs.push(this.item$.subscribe((item) => this.item = item)); this.subs.push(collection$.subscribe((collection) => this.collection = collection)); + } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 0ed0863409..57ab7d66d8 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -1,10 +1,10 @@
- + - - + + diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts index 5eda1372eb..7f7de632dd 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.ts @@ -48,6 +48,7 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp } ngOnInit() { + // console.log('ngOnInit', this.model, this.model.value); this.searchOptions = new IntegrationSearchOptions( this.model.authorityOptions.scope, this.model.authorityOptions.name, @@ -65,6 +66,7 @@ export class DsDynamicScrollableDropdownComponent extends DynamicFormControlComp }), first()) .subscribe((object: IntegrationData) => { + // console.log('ngOnInit subscribe', object, this.model, this.model.value); this.optionsList = object.payload; if (this.model.value) { this.setCurrentValue(this.model.value); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts index 24f75801a3..f61eba461a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts @@ -163,13 +163,10 @@ export class RelationshipEffects { this.relationshipService.getRelationshipByItemsAndLabel(item1, item2, relationshipType).pipe( take(1), hasValueOperator(), - tap((v) => console.log('before delete', v)), mergeMap((relationship: Relationship) => this.relationshipService.deleteRelationship(relationship.id, 'none')), take(1), - tap((v) => console.log('before refresh', v)), switchMap(() => this.refreshWorkspaceItemInCache(submissionId)), ).subscribe((submissionObject: SubmissionObject) => { - console.log('in subscribe', submissionObject); this.store.dispatch(new SaveSubmissionSectionFormSuccessAction(submissionId, [submissionObject], false)) }); } diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts index 33a92c726d..09a3f53c58 100644 --- a/src/app/shared/form/builder/parsers/concat-field-parser.ts +++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts @@ -13,7 +13,7 @@ import { DynamicConcatModel, DynamicConcatModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-concat.model'; -import { isNotEmpty } from '../../../empty.util'; +import { hasNoValue, hasValue, isNotEmpty } from '../../../empty.util'; import { ParserOptions } from './parser-options'; import { CONFIG_DATA, @@ -53,14 +53,18 @@ export class ConcatFieldParser extends FieldParser { }; const groupId = id.replace(/\./g, '_') + CONCAT_GROUP_SUFFIX; - const concatGroup: DynamicConcatModelConfig = this.initModel(groupId, label, false); + const concatGroup: DynamicConcatModelConfig = this.initModel(groupId, label, false, true); concatGroup.group = []; concatGroup.separator = this.separator; const input1ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_FIRST_INPUT_SUFFIX, false, false); const input2ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_SECOND_INPUT_SUFFIX, false, false); - input2ModelConfig.hint = ' '; + + if (hasNoValue(concatGroup.hint) && hasValue(input1ModelConfig.hint) && hasNoValue(input2ModelConfig.hint)) { + concatGroup.hint = input1ModelConfig.hint; + input1ModelConfig.hint = undefined; + } if (this.configData.mandatory) { concatGroup.required = true; diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index 16b0a7a7ce..c37dcaedea 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -78,12 +78,7 @@ export abstract class FieldParser { } } model = this.modelFactory(fieldValue, false); - if (!isFirstModelInArray) { - model.hint = undefined; - if (Array.isArray(model.group)) { - model.group.forEach((group) => group.hint = undefined); - } - } + model.id = `${model.id}_${fieldArrayCounter}`; } setLayout(model, 'element', 'host', 'col'); if (model.hasLanguages || isNotEmpty(model.relationship)) { diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 8c349a5941..ff749027ba 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -23,6 +23,9 @@ import { Observable, Subscription } from 'rxjs'; import { hasValue, isNotEmpty, isNotNull, isNull } from '../empty.util'; import { FormService } from './form.service'; import { FormEntry, FormError } from './form.reducer'; +import { FormFieldMetadataValueObject } from "./builder/models/form-field-metadata-value.model"; +import { AuthorityValue } from "../../core/integration/models/authority.value"; +import { DsDynamicInputModel } from "./builder/ds-dynamic-form-ui/models/ds-dynamic-input.model"; /** * The default form component. @@ -301,14 +304,33 @@ export class FormComponent implements OnDestroy, OnInit { insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void { const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray; + + // First emit the new value so it can be sent to the server + const value = formArrayControl.controls[0].value; + const event = this.getEvent($event, arrayContext, 0, 'add'); + this.addArrayItem.emit(event); + this.change.emit(event); + + // Next: update the UI so the user sees the changes + // without having to wait for the server's reply + + // add an empty new field at the bottom this.formBuilderService.addFormArrayGroup(formArrayControl, arrayContext); - this.addArrayItem.emit(this.getEvent($event, arrayContext, index, 'add')); - const value = formArrayControl.controls[index].value; - formArrayControl.controls[formArrayControl.length - 1].setValue(value); + // set that field to the new value + const model = arrayContext.groups[arrayContext.groups.length - 1].group[0] as any; + if (model.type === 'SCROLLABLE_DROPDOWN') { + model.value = Object.values(value)[0]; + } else { + formArrayControl.controls[formArrayControl.length - 1].setValue(value); + } - this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext); - this.formBuilderService.insertFormArrayGroup(index, formArrayControl, arrayContext); + // Clear the topmost field by removing the filled out version and inserting a new, empty version. + // Doing it this way ensures an empty value of the correct type is added without a bunch of ifs here + this.formBuilderService.removeFormArrayGroup(0, formArrayControl, arrayContext); + this.formBuilderService.insertFormArrayGroup(0, formArrayControl, arrayContext); + + // Tell the formService that it should rerender. this.formService.changeForm(this.formId, this.formModel); } diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 60c8b9a7a3..908f473136 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -94,7 +94,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { } else { this.submissionId = submissionObjectRD.payload.id.toString(); this.collectionId = (submissionObjectRD.payload.collection as Collection).id; - this.selfUrl = submissionObjectRD.payload.self; + this.selfUrl = submissionObjectRD.payload._links.self.href; this.sections = submissionObjectRD.payload.sections; this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel); this.changeDetectorRef.detectChanges(); diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index 90aa62b6d3..a6d9fec995 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -334,7 +334,6 @@ export class SubmissionObjectEffects { if (notify && !currentState.sections[sectionId].enabled) { this.submissionService.notifyNewSection(submissionId, sectionId, currentState.sections[sectionId].sectionType); } - console.log(submissionId, sectionId, sectionData, sectionErrors); mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, sectionErrors)); } }); diff --git a/src/app/submission/sections/form/section-form-operations.service.ts b/src/app/submission/sections/form/section-form-operations.service.ts index 79489929ab..2741b9f1bd 100644 --- a/src/app/submission/sections/form/section-form-operations.service.ts +++ b/src/app/submission/sections/form/section-form-operations.service.ts @@ -69,6 +69,9 @@ export class SectionFormOperationsService { case 'change': this.dispatchOperationsFromChangeEvent(pathCombiner, event, previousValue, hasStoredValue); break; + case 'add': + this.dispatchOperationsFromAddEvent(pathCombiner, event); + break; default: break; } @@ -294,6 +297,41 @@ export class SectionFormOperationsService { } } + /** + * Handle form add operations + * + * @param pathCombiner + * the [[JsonPatchOperationPathCombiner]] object for the specified operation + * @param event + * the [[DynamicFormControlEvent]] for the specified operation + */ + protected dispatchOperationsFromAddEvent(pathCombiner: JsonPatchOperationPathCombiner, + event: DynamicFormControlEvent + ): void { + const path = this.getFieldPathSegmentedFromChangeEvent(event); + const value = this.getFieldValueFromChangeEvent(event); + if (isNotEmpty(value)) { + value.place = this.getArrayIndexFromEvent(event); + if (hasValue(event.group) && hasValue(event.group.value)) { + const valuesInGroup = event.group.value + .map((g) => Object.values(g)) + .reduce((accumulator, currentValue) => accumulator.concat(currentValue)) + .filter((v) => isNotEmpty(v)); + if (valuesInGroup.length === 1) { + // The first add for a field needs to be a different PATCH operation + // for some reason + this.operationsBuilder.add( + pathCombiner.getPath([path]), + [value], false); + } else { + this.operationsBuilder.add( + pathCombiner.getPath([path, '-']), + value, false); + } + } + } + } + /** * Handle form change operations *