-
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html
index 03ef45c7a4..cf2dc3e61c 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
index dbc3a42a05..290635ea27 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
index 8f74452eaa..6f229c00e0 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html
index f08d0fdc11..5f570cb021 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html
@@ -1,4 +1,4 @@
-
+
+
-
+
diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html
index d8a4e744e4..de805a64b3 100644
--- a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html
+++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html
@@ -1,7 +1,6 @@
- 0"
- class="item-list-job-title">
+
@@ -9,5 +8,5 @@
0 ? descTemplate : null">
+ [tooltip]="metadataRepresentation.allMetadata(['dc.description']).length > 0 ? descTemplate : null">
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts
index 4612996e91..7d39d4d314 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component.ts
@@ -7,7 +7,7 @@ import { Component, Inject, OnInit } from '@angular/core';
import { Metadata } from '../../../../../core/shared/metadata.utils';
import { MetadataValue } from '../../../../../core/shared/metadata.models';
-@listableObjectComponent(ExternalSourceEntry, ViewMode.ListElement, Context.SubmissionModal)
+@listableObjectComponent(ExternalSourceEntry, ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
@Component({
selector: 'ds-external-source-entry-list-submission-element',
styleUrls: ['./external-source-entry-list-submission-element.component.scss'],
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html
index 93165c24cd..063e1393cc 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.html
@@ -1,10 +1,15 @@
-
-
-
+
+
+
-
+
+
+
0"
class="item-list-address-locality">
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
index 96f28a799b..1ed9d6cead 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts
@@ -20,7 +20,8 @@ import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
-@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SubmissionModal)
+@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal)
+@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
@Component({
selector: 'ds-person-search-result-list-submission-element',
styleUrls: ['./org-unit-search-result-list-submission-element.component.scss'],
@@ -34,6 +35,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
allSuggestions: string[];
selectedName: string;
alternativeField = 'dc.title.alternative';
+ useNameVariants = false;
constructor(protected truncatableService: TruncatableService,
private relationshipService: RelationshipService,
@@ -48,16 +50,21 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
ngOnInit() {
super.ngOnInit();
- const defaultValue = this.firstMetadataValue('organization.legalName');
- const alternatives = this.allMetadataValues(this.alternativeField);
- this.allSuggestions = [defaultValue, ...alternatives];
- this.relationshipService.getNameVariant(this.listID, this.dso.uuid)
- .pipe(take(1))
- .subscribe((nameVariant: string) => {
- this.selectedName = nameVariant || defaultValue;
- }
- );
+ this.useNameVariants = this.context === Context.EntitySearchModalWithNameVariants;
+
+ if (this.useNameVariants) {
+ const defaultValue = this.firstMetadataValue('organization.legalName');
+ const alternatives = this.allMetadataValues(this.alternativeField);
+ this.allSuggestions = [defaultValue, ...alternatives];
+
+ this.relationshipService.getNameVariant(this.listID, this.dso.uuid)
+ .pipe(take(1))
+ .subscribe((nameVariant: string) => {
+ this.selectedName = nameVariant || defaultValue;
+ }
+ );
+ }
}
select(value) {
@@ -75,7 +82,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
if (!this.allSuggestions.includes(value)) {
this.openModal(value)
.then(() => {
-
+ // user clicked ok: store the name variant in the item
const newName: MetadataValue = new MetadataValue();
newName.value = value;
@@ -89,9 +96,12 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
},
});
this.itemDataService.update(updatedItem).pipe(take(1)).subscribe();
- })
+ }).catch(() => {
+ // user clicked cancel: use the name variant only for this relation, no further action required
+ }).finally(() => {
+ this.select(value);
+ })
}
- this.select(value);
}
openModal(value): Promise {
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.scss b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.scss
index 8301e12c5f..20b48c805b 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.scss
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component.scss
@@ -1,6 +1,7 @@
form {
z-index: 1;
&:before {
+ pointer-events: none; // prevent the icon from ‘catching‘ the click
position: absolute;
font-weight: 900;
font-family: "Font Awesome 5 Free";
@@ -15,4 +16,4 @@ form {
input.suggestion_input {
background: transparent;
}
-}
\ No newline at end of file
+}
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html
index 25c091d386..9fe9898c2b 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.html
@@ -1,7 +1,4 @@
-
-
-
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts
index 83761c6c20..9541ff334c 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts
@@ -20,7 +20,7 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models';
import { ItemDataService } from '../../../../../core/data/item-data.service';
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
-@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SubmissionModal)
+@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
@Component({
selector: 'ds-person-search-result-list-submission-element',
styleUrls: ['./person-search-result-list-submission-element.component.scss'],
@@ -55,7 +55,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
this.relationshipService.getNameVariant(this.listID, this.dso.uuid)
.pipe(take(1))
.subscribe((nameVariant: string) => {
- this.selectedName = nameVariant || defaultValue;
+ this.selectedName = nameVariant || defaultValue;
}
);
}
@@ -75,27 +75,32 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
if (!this.allSuggestions.includes(value)) {
this.openModal(value)
.then(() => {
+ // user clicked ok: store the name variant in the item
+ const newName: MetadataValue = new MetadataValue();
+ newName.value = value;
- const newName: MetadataValue = new MetadataValue();
- newName.value = value;
-
- const existingNames: MetadataValue[] = this.dso.metadata[this.alternativeField] || [];
- const alternativeNames = { [this.alternativeField]: [...existingNames, newName] };
- const updatedItem =
- Object.assign({}, this.dso, {
- metadata: {
- ...this.dso.metadata,
- ...alternativeNames
- },
- });
- this.itemDataService.update(updatedItem).pipe(take(1)).subscribe();
- })
+ const existingNames: MetadataValue[] = this.dso.metadata[this.alternativeField] || [];
+ const alternativeNames = { [this.alternativeField]: [...existingNames, newName] };
+ const updatedItem =
+ Object.assign({}, this.dso, {
+ metadata: {
+ ...this.dso.metadata,
+ ...alternativeNames
+ },
+ });
+ this.itemDataService.update(updatedItem).pipe(take(1)).subscribe();
+ this.itemDataService.commitUpdates();
+ }).catch(() => {
+ // user clicked cancel: use the name variant only for this relation, no further action required
+ }).finally(() => {
+ this.select(value);
+ })
}
- this.select(value);
}
openModal(value): Promise {
const modalRef = this.modalService.open(NameVariantModalComponent, { centered: true });
+
const modalComp = modalRef.componentInstance;
modalComp.value = value;
return modalRef.result;
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.html b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.html
index e177b2b561..062e68da1f 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.html
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.html
@@ -1,11 +1,12 @@
-
-
\ No newline at end of file
+
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.scss b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.scss
index 8301e12c5f..86233c473a 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.scss
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.scss
@@ -1,6 +1,6 @@
form {
- z-index: 1;
&:before {
+ pointer-events: none; // prevent the icon from ‘catching‘ the click
position: absolute;
font-weight: 900;
font-family: "Font Awesome 5 Free";
@@ -9,10 +9,9 @@ form {
right: 0;
height: 20px;
width: 20px;
- z-index: -1;
}
input.suggestion_input {
background: transparent;
}
-}
\ No newline at end of file
+}
diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.ts
index a1802ce1a7..5b4ecd9d2e 100644
--- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.ts
+++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component.ts
@@ -33,8 +33,10 @@ export class PersonInputSuggestionsComponent extends InputSuggestionsComponent i
}
onSubmit(data) {
- this.value = data;
- this.submitSuggestion.emit(data);
+ if (data !== this.value) {
+ this.value = data;
+ this.submitSuggestion.emit(data);
+ }
}
onClickSuggestion(data) {
diff --git a/src/app/process-page/form/process-form.component.spec.ts b/src/app/process-page/form/process-form.component.spec.ts
index 95f266ed6f..0afdf78201 100644
--- a/src/app/process-page/form/process-form.component.spec.ts
+++ b/src/app/process-page/form/process-form.component.spec.ts
@@ -19,6 +19,7 @@ describe('ProcessFormComponent', () => {
let component: ProcessFormComponent;
let fixture: ComponentFixture;
let scriptService;
+ let router;
let parameterValues;
let script;
@@ -41,7 +42,10 @@ describe('ProcessFormComponent', () => {
}
})
}
- )
+ );
+ router = {
+ navigateByUrl: () => undefined,
+ };
}
beforeEach(async(() => {
diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
index a3f6ac0216..e791f41d56 100644
--- a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
+++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts
@@ -48,7 +48,7 @@ describe('DeleteComColPageComponent', () => {
dsoDataService = jasmine.createSpyObj(
'dsoDataService',
{
- delete: observableOf(true)
+ delete: observableOf({ isSuccessful: true })
});
routerStub = {
@@ -106,7 +106,7 @@ describe('DeleteComColPageComponent', () => {
});
it('should show an error notification on failure', () => {
- (dsoDataService.delete as any).and.returnValue(observableOf(false));
+ (dsoDataService.delete as any).and.returnValue(observableOf({ isSuccessful: false }));
spyOn(router, 'navigate');
comp.onConfirm(data2);
fixture.detectChanges();
diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
index f5a1a84af5..d07d7be032 100644
--- a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
+++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts
@@ -7,6 +7,7 @@ import { DataService } from '../../../core/data/data.service';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { NotificationsService } from '../../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
+import { RestResponse } from '../../../core/cache/response.models';
/**
* Component representing the delete page for communities and collections
@@ -45,8 +46,8 @@ export class DeleteComColPageComponent implements
onConfirm(dso: TDomain) {
this.dsoDataService.delete(dso.id)
.pipe(first())
- .subscribe((success: boolean) => {
- if (success) {
+ .subscribe((response: RestResponse) => {
+ if (response.isSuccessful) {
const successMessage = this.translate.instant((dso as any).type + '.delete.notification.success');
this.notifications.success(successMessage)
} else {
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 a31171d7ef..028299f760 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
@@ -1,29 +1,25 @@
-
-
-
-
-
+
+
0)}">
-
-
-
- {{ message | translate:model.validators }}
+ {{ message | translate: model.validators }}
-
-
0" class="col-xs-2">
+
0" class="col-xs-2" >
{{lang.display}}
-
-
+
0">
{{'form.lookup' | translate}}
-
-
+
+
+
+
+
+
-
-
-
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
index 4dee6905d2..7b95f2396e 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
@@ -71,6 +71,9 @@ import { Item } from '../../../../core/shared/item.model';
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
import { of as observableOf } from 'rxjs';
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
+import { FormService } from '../../form.service';
+import { SubmissionService } from '../../../../submission/submission.service';
+import { FormBuilderService } from '../form-builder.service';
describe('DsDynamicFormControlContainerComponent test suite', () => {
@@ -101,15 +104,16 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
new DynamicSwitchModel({ id: 'switch' }),
new DynamicTextAreaModel({ id: 'textarea' }),
new DynamicTimePickerModel({ id: 'timepicker' }),
- new DynamicTypeaheadModel({ id: 'typeahead', metadataFields: [], repeatable: false, submissionId: '1234' }),
+ new DynamicTypeaheadModel({ id: 'typeahead', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
new DynamicScrollableDropdownModel({
id: 'scrollableDropdown',
authorityOptions: authorityOptions,
metadataFields: [],
repeatable: false,
- submissionId: '1234'
+ submissionId: '1234',
+ hasSelectableMetadata: false
}),
- new DynamicTagModel({ id: 'tag', metadataFields: [], repeatable: false, submissionId: '1234' }),
+ new DynamicTagModel({ id: 'tag', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
new DynamicListCheckboxGroupModel({
id: 'checkboxList',
authorityOptions: authorityOptions,
@@ -130,11 +134,12 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
scopeUUID: '',
submissionScope: '',
repeatable: false,
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
}),
new DynamicDsDatePickerModel({ id: 'datepicker' }),
- new DynamicLookupModel({ id: 'lookup', metadataFields: [], repeatable: false, submissionId: '1234' }),
- new DynamicLookupNameModel({ id: 'lookupName', metadataFields: [], repeatable: false, submissionId: '1234' }),
+ new DynamicLookupModel({ id: 'lookup', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false }),
+ new DynamicLookupNameModel({ id: 'lookupName', metadataFields: [], repeatable: false, submissionId: '1234', hasSelectableMetadata: false}),
new DynamicQualdropModel({ id: 'combobox', readOnly: false, required: false })
];
const testModel = formModel[8];
@@ -175,6 +180,9 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
{ provide: Store, useValue: {} },
{ provide: RelationshipService, useValue: {} },
{ provide: SelectableListService, useValue: {} },
+ { provide: FormService, useValue: {} },
+ { provide: FormBuilderService, useValue: {} },
+ { provide: SubmissionService, useValue: {} },
{
provide: SubmissionObjectDataService,
useValue: {
@@ -220,7 +228,6 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
expect(component.group instanceof FormGroup).toBe(true);
expect(component.model instanceof DynamicFormControlModel).toBe(true);
expect(component.hasErrorMessaging).toBe(false);
- expect(component.asBootstrapFormGroup).toBe(true);
expect(component.onControlValueChanges).toBeDefined();
expect(component.onModelDisabledUpdates).toBeDefined();
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 2089ce8bca..0064c2e093 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
@@ -1,5 +1,6 @@
import {
- ChangeDetectionStrategy, ChangeDetectorRef,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
Component,
ComponentFactoryResolver,
ContentChildren,
@@ -16,7 +17,7 @@ import {
ViewChild,
ViewContainerRef
} from '@angular/core';
-import { FormGroup } from '@angular/forms';
+import { FormArray, FormGroup } from '@angular/forms';
import {
DYNAMIC_FORM_CONTROL_TYPE_ARRAY,
@@ -29,13 +30,18 @@ import {
DYNAMIC_FORM_CONTROL_TYPE_SELECT,
DYNAMIC_FORM_CONTROL_TYPE_TEXTAREA,
DYNAMIC_FORM_CONTROL_TYPE_TIMEPICKER,
- DynamicDatePickerModel, DynamicFormComponentService,
+ DynamicDatePickerModel,
+ DynamicFormArrayGroupModel,
+ DynamicFormArrayModel,
+ DynamicFormComponentService,
DynamicFormControl,
DynamicFormControlContainerComponent,
DynamicFormControlEvent,
+ DynamicFormControlEventType,
DynamicFormControlModel,
DynamicFormLayout,
- DynamicFormLayoutService, DynamicFormRelationService,
+ DynamicFormLayoutService,
+ DynamicFormRelationService,
DynamicFormValidationService,
DynamicTemplateDirective,
} from '@ng-dynamic-forms/core';
@@ -50,11 +56,7 @@ import {
DynamicNGBootstrapTimePickerComponent
} from '@ng-dynamic-forms/ui-ng-bootstrap';
import { TranslateService } from '@ngx-translate/core';
-import { followLink } from '../../../utils/follow-link-config.model';
-import {
- Reorderable,
- ReorderableRelationship
-} from './existing-metadata-list-element/existing-metadata-list-element.component';
+import { ReorderableRelationship } from './existing-metadata-list-element/existing-metadata-list-element.component';
import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model';
import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
@@ -63,7 +65,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 { hasValue, isNotEmpty, isNotUndefined } from '../../../empty.util';
+import { hasNoValue, hasValue, 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';
@@ -78,8 +80,8 @@ import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './models/relation-grou
import { DsDatePickerInlineComponent } from './models/date-picker-inline/dynamic-date-picker-inline.component';
import { DYNAMIC_FORM_CONTROL_TYPE_CUSTOM_SWITCH } from './models/custom-switch/custom-switch.model';
import { CustomSwitchComponent } from './models/custom-switch/custom-switch.component';
-import { map, startWith, switchMap, find } from 'rxjs/operators';
-import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
+import { find, map, startWith, switchMap, take } 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';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@@ -88,7 +90,7 @@ import { SelectableListService } from '../../../object-list/selectable-list/sele
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
-import { getAllSucceededRemoteData, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
+import { getAllSucceededRemoteData, getFirstSucceededRemoteDataPayload, getPaginatedListPayload, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
@@ -98,9 +100,16 @@ import { SubmissionObjectDataService } from '../../../../core/submission/submiss
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
-import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { Collection } from '../../../../core/shared/collection.model';
+import { MetadataValue, VIRTUAL_METADATA_PREFIX } from '../../../../core/shared/metadata.models';
+import { FormService } from '../../form.service';
+import { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
+import { SubmissionService } from '../../../../submission/submission.service';
+import { followLink } from '../../../utils/follow-link-config.model';
+import { paginatedRelationsToItems } from '../../../../+item-page/simple/item-types/shared/item-relationships-utils';
+import { RelationshipOptions } from '../models/relationship-options.model';
+import { FormBuilderService } from '../form-builder.service';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type
| null {
switch (model.type) {
@@ -180,22 +189,22 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
@Input('templates') inputTemplateList: QueryList;
@Input() formId: string;
- @Input() asBootstrapFormGroup = true;
+ @Input() asBootstrapFormGroup = false;
@Input() bindId = true;
@Input() context: any | null = null;
@Input() group: FormGroup;
@Input() hasErrorMessaging = false;
@Input() layout = null as DynamicFormLayout;
@Input() model: any;
- reorderables$: Observable;
- reorderables: ReorderableRelationship[];
- hasRelationLookup: boolean;
+ relationshipValue$: Observable;
+ isRelationship: boolean;
modalRef: NgbModalRef;
item: Item;
+ item$: Observable- ;
collection: Collection;
listId: string;
searchConfig: string;
-
+ value: MetadataValue;
/**
* List of subscriptions to unsubscribe from
*/
@@ -207,7 +216,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
@Output('dfFocus') focus: EventEmitter
= new EventEmitter();
@Output('ngbEvent') customEvent: EventEmitter = new EventEmitter();
/* tslint:enable:no-output-rename */
- @ViewChild('componentViewContainer', { read: ViewContainerRef, static: true}) componentViewContainerRef: ViewContainerRef;
+ @ViewChild('componentViewContainer', { read: ViewContainerRef, static: true }) componentViewContainerRef: ViewContainerRef;
private showErrorMessagesPreviousStage: boolean;
@@ -229,9 +238,11 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
private zone: NgZone,
private store: Store,
private submissionObjectService: SubmissionObjectDataService,
- private ref: ChangeDetectorRef
+ private ref: ChangeDetectorRef,
+ private formService: FormService,
+ private formBuilderService: FormBuilderService,
+ private submissionService: SubmissionService
) {
-
super(componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService);
}
@@ -239,62 +250,76 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
* Sets up the necessary variables for when this control can be used to add relationships to the submitted item
*/
ngOnInit(): void {
- this.hasRelationLookup = hasValue(this.model.relationship);
- this.reorderables = [];
- if (this.hasRelationLookup) {
+ this.isRelationship = hasValue(this.model.relationship);
+ const isWrapperAroundRelationshipList = hasValue(this.model.relationshipConfig);
- this.listId = 'list-' + this.model.relationship.relationshipType;
+ if (this.isRelationship || isWrapperAroundRelationshipList) {
+ const config = this.model.relationshipConfig || this.model.relationship;
+ const relationshipOptions = Object.assign(new RelationshipOptions(), config);
+ this.listId = `list-${this.model.submissionId}-${relationshipOptions.relationshipType}`;
+ this.setItem();
- const submissionObject$ = this.submissionObjectService
- .findById(this.model.submissionId, followLink('item'), followLink('collection')).pipe(
- getAllSucceededRemoteData(),
- getRemoteDataPayload()
- );
+ if (isWrapperAroundRelationshipList || !this.model.repeatable) {
+ const subscription = this.selectableListService.getSelectableList(this.listId).pipe(
+ find((list: SelectableListState) => hasNoValue(list)),
+ switchMap(() => this.item$.pipe(take(1))),
+ switchMap((item) => {
+ const relationshipsRD$ = this.relationshipService.getItemRelationshipsByLabel(item,
+ relationshipOptions.relationshipType,
+ undefined,
+ followLink('leftItem'),
+ followLink('rightItem'),
+ followLink('relationshipType')
+ );
- const item$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
- const collection$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.collection as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+ relationshipsRD$.pipe(
+ getFirstSucceededRemoteDataPayload(),
+ getPaginatedListPayload()
+ ).subscribe((relationships: Relationship[]) => {
+ // set initial namevariants for pre-existing relationships
+ relationships.forEach((relationship: Relationship) => {
+ const relationshipMD: MetadataValue = item.firstMetadata(relationshipOptions.metadataField, { authority: `${VIRTUAL_METADATA_PREFIX}${relationship.id}` });
+ const nameVariantMD: MetadataValue = item.firstMetadata(this.model.metadataFields, { authority: `${VIRTUAL_METADATA_PREFIX}${relationship.id}` });
+ if (hasValue(relationshipMD) && isNotEmpty(relationshipMD.value) && hasValue(nameVariantMD) && isNotEmpty(nameVariantMD.value)) {
+ this.relationshipService.setNameVariant(this.listId, relationshipMD.value, nameVariantMD.value);
+ }
+ });
+ });
- this.subs.push(item$.subscribe((item) => this.item = item));
- this.subs.push(collection$.subscribe((collection) => this.collection = collection));
- this.reorderables$ = item$.pipe(
- switchMap((item) => this.relationshipService.getItemRelationshipsByLabel(item, this.model.relationship.relationshipType, undefined, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType'))
+ return relationshipsRD$.pipe(
+ paginatedRelationsToItems(item.uuid),
+ getSucceededRemoteData(),
+ map((items: RemoteData>) => items.payload.page.map((i) => Object.assign(new ItemSearchResult(), { indexableObject: i }))),
+ )
+ })
+ ).subscribe((relatedItems: Array>) => this.selectableListService.select(this.listId, relatedItems));
+ this.subs.push(subscription);
+ }
+
+ if (hasValue(this.model.metadataValue)) {
+ this.value = Object.assign(new MetadataValue(), this.model.metadataValue);
+ } else {
+ this.value = Object.assign(new MetadataValue(), this.model.value);
+ }
+
+ if (hasValue(this.value) && this.value.isVirtual) {
+ const relationship$ = this.relationshipService.findById(this.value.virtualValue, followLink('leftItem'), followLink('rightItem'), followLink('relationshipType'))
.pipe(
getAllSucceededRemoteData(),
- getRemoteDataPayload(),
- map((relationshipList: PaginatedList) => relationshipList.page),
- startWith([]),
- switchMap((relationships: Relationship[]) =>
- observableCombineLatest(
- relationships.map((relationship: Relationship) =>
- relationship.leftItem.pipe(
- getSucceededRemoteData(),
- getRemoteDataPayload(),
- map((leftItem: Item) => {
- return new ReorderableRelationship(relationship, leftItem.uuid !== this.item.uuid)
- }),
- )
- ))),
- map((relationships: ReorderableRelationship[]) =>
- relationships
- .sort((a: Reorderable, b: Reorderable) => {
- return Math.sign(a.getPlace() - b.getPlace());
- })
+ getRemoteDataPayload());
+ this.relationshipValue$ = observableCombineLatest([this.item$.pipe(take(1)), relationship$]).pipe(
+ switchMap(([item, relationship]: [Item, Relationship]) =>
+ relationship.leftItem.pipe(
+ getAllSucceededRemoteData(),
+ getRemoteDataPayload(),
+ map((leftItem: Item) => {
+ return new ReorderableRelationship(relationship, leftItem.uuid !== item.uuid, this.relationshipService, this.store, this.model.submissionId)
+ }),
)
- )
- )
- );
-
- this.subs.push(this.reorderables$.subscribe((rs) => {
- this.reorderables = rs;
- this.ref.detectChanges();
- }));
-
- item$.pipe(
- switchMap((item) => this.relationshipService.getRelatedItemsByLabel(item, this.model.relationship.relationshipType)),
- map((items: RemoteData>) => items.payload.page.map((item) => Object.assign(new ItemSearchResult(), { indexableObject: item }))),
- ).subscribe((relatedItems: Array>) => {
- this.selectableListService.select(this.listId, relatedItems)
- });
+ ),
+ startWith(undefined)
+ );
+ }
}
}
@@ -303,7 +328,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
ngOnChanges(changes: SimpleChanges) {
- if (changes) {
+ if (changes && !this.isRelationship && hasValue(this.group.get(this.model.id))) {
super.ngOnChanges(changes);
if (this.model && this.model.placeholder) {
this.model.placeholder = this.translateService.instant(this.model.placeholder);
@@ -351,6 +376,27 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
size: 'lg'
});
const modalComp = this.modalRef.componentInstance;
+
+ if (hasValue(this.model.value) && !this.model.readOnly) {
+ if (typeof this.model.value === 'string') {
+ modalComp.query = this.model.value;
+ } else if (typeof this.model.value.value === 'string') {
+ modalComp.query = this.model.value.value;
+ }
+ }
+
+ if (hasValue(this.model.value)) {
+ 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);
+
modalComp.repeatable = this.model.repeatable;
modalComp.listId = this.listId;
modalComp.relationshipOptions = this.model.relationship;
@@ -358,32 +404,18 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
modalComp.metadataFields = this.model.metadataFields;
modalComp.item = this.item;
modalComp.collection = this.collection;
+ modalComp.submissionId = this.model.submissionId;
}
/**
- * Method to move a relationship inside the list of relationships
- * This will update the view and update the right or left place field of the relationships in the list
- * @param event
+ * Callback for the remove event,
+ * remove the current control from its array
*/
- moveSelection(event: CdkDragDrop) {
- this.zone.runOutsideAngular(() => {
- moveItemInArray(this.reorderables, event.previousIndex, event.currentIndex);
- const reorderables: Reorderable[] = this.reorderables.map((reo: Reorderable, index: number) => {
- reo.oldIndex = reo.getPlace();
- reo.newIndex = index;
- return reo;
- }
- );
- observableCombineLatest(
- reorderables.map((rel: ReorderableRelationship) => {
- if (rel.oldIndex !== rel.newIndex) {
- return this.relationshipService.updatePlace(rel);
- } else {
- return observableOf(undefined) as Observable>;
- }
- })
- ).subscribe();
- })
+ onRemove(): void {
+ const arrayContext: DynamicFormArrayModel = (this.context as DynamicFormArrayGroupModel).context;
+ const path = this.formBuilderService.getPath(arrayContext);
+ const formArrayControl = this.group.root.get(path) as FormArray;
+ this.formBuilderService.removeFormArrayGroup(this.context.index, formArrayControl, arrayContext);
}
/**
@@ -396,9 +428,20 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
/**
- * Prevent unnecessary rerendering so fields don't lose focus
+ * Initialize this.item$ based on this.model.submissionId
*/
- trackReorderable(index, reorderable: Reorderable) {
- return hasValue(reorderable) ? reorderable.getId() : undefined;
+ private setItem() {
+ const submissionObject$ = this.submissionObjectService
+ .findById(this.model.submissionId, followLink('item'), followLink('collection')).pipe(
+ getAllSucceededRemoteData(),
+ getRemoteDataPayload()
+ );
+
+ this.item$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+ const collection$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.collection as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+
+ 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/ds-dynamic-form.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.html
index 4d8123a4b9..5684f4eac9 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.html
@@ -7,6 +7,7 @@
[model]="model"
[ngClass]="[getClass(model, 'element', 'host'), getClass(model, 'grid', 'host')]"
[templates]="templates"
+ [asBootstrapFormGroup]="true"
(dfBlur)="onEvent($event, 'blur')"
(dfChange)="onEvent($event, 'change')"
(dfFocus)="onEvent($event, 'focus')">
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts
index 490275a03b..ad1c18706d 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form.component.ts
@@ -37,5 +37,4 @@ export class DsDynamicFormComponent extends DynamicFormComponent {
constructor(protected formService: FormBuilderService, protected layoutService: DynamicFormLayoutService) {
super(formService, layoutService);
}
-
}
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 960dd78767..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,11 +1,14 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
- ×
-
-
-
-
-
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.scss
index e69de29bb2..ab63e324bd 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.scss
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.scss
@@ -0,0 +1,3 @@
+span.text-contents{
+ padding: $btn-padding-y 0;
+}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
index 79a650b597..ff2fd0c798 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
@@ -10,6 +10,8 @@ import { RelationshipOptions } from '../../models/relationship-options.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
+import { of as observableOf } from 'rxjs';
+import { RelationshipService } from '../../../../../core/data/relationship.service';
describe('ExistingMetadataListElementComponent', () => {
let component: ExistingMetadataListElementComponent;
@@ -28,6 +30,8 @@ describe('ExistingMetadataListElementComponent', () => {
let leftItemRD$;
let rightItemRD$;
let relatedSearchResult;
+ let submissionId;
+ let relationshipService;
function init() {
uuid1 = '91ce578d-2e63-4093-8c73-3faafd716000';
@@ -42,9 +46,13 @@ describe('ExistingMetadataListElementComponent', () => {
leftItemRD$ = createSuccessfulRemoteDataObject$(relatedItem);
rightItemRD$ = createSuccessfulRemoteDataObject$(submissionItem);
relatedSearchResult = Object.assign(new ItemSearchResult(), { indexableObject: relatedItem });
+ relationshipService = {
+ updatePlace:() => observableOf({})
+ } as any;
relationship = Object.assign(new Relationship(), { leftItem: leftItemRD$, rightItem: rightItemRD$ });
- reoRel = new ReorderableRelationship(relationship, true);
+ submissionId = '1234';
+ reoRel = new ReorderableRelationship(relationship, true, relationshipService, {} as any, submissionId);
}
beforeEach(async(() => {
@@ -68,6 +76,7 @@ describe('ExistingMetadataListElementComponent', () => {
component.reoRel = reoRel;
component.metadataFields = metadataFields;
component.relationshipOptions = relationshipOptions;
+ component.submissionId = submissionId;
fixture.detectChanges();
component.ngOnChanges();
});
@@ -84,9 +93,8 @@ describe('ExistingMetadataListElementComponent', () => {
it('should dispatch a RemoveRelationshipAction', () => {
component.removeSelection();
- const action = new RemoveRelationshipAction(submissionItem, relatedItem, relationshipOptions.relationshipType);
+ const action = new RemoveRelationshipAction(submissionItem, relatedItem, relationshipOptions.relationshipType, submissionId);
expect(store.dispatch).toHaveBeenCalledWith(action);
-
});
})
});
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts
index 09aaa253c6..d4ce3342e7 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts
@@ -1,50 +1,126 @@
-import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
+import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
+import { FormControl } from '@angular/forms';
+import { DynamicFormArrayGroupModel } from '@ng-dynamic-forms/core';
+import { Store } from '@ngrx/store';
+import { BehaviorSubject, Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { AppState } from '../../../../../app.reducer';
+import { RelationshipService } from '../../../../../core/data/relationship.service';
+import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
import { Item } from '../../../../../core/shared/item.model';
+import { ItemMetadataRepresentation } from '../../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { MetadataRepresentation } from '../../../../../core/shared/metadata-representation/metadata-representation.model';
+import { MetadataValue } from '../../../../../core/shared/metadata.models';
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators';
import { hasValue, isNotEmpty } from '../../../../empty.util';
-import { Subscription } from 'rxjs';
-import { filter } from 'rxjs/operators';
-import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
-import { MetadataValue } from '../../../../../core/shared/metadata.models';
-import { ItemMetadataRepresentation } from '../../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
-import { RelationshipOptions } from '../../models/relationship-options.model';
-import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
-import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
-import { Store } from '@ngrx/store';
-import { AppState } from '../../../../../app.reducer';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
+import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
+import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
+import { RelationshipOptions } from '../../models/relationship-options.model';
+import { DynamicConcatModel } from '../models/ds-dynamic-concat.model';
+import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
// tslint:disable:max-classes-per-file
/**
* Abstract class that defines objects that can be reordered
*/
export abstract class Reorderable {
+
constructor(public oldIndex?: number, public newIndex?: number) {
}
+ /**
+ * Return the id for this Reorderable
+ */
abstract getId(): string;
+ /**
+ * Return the place metadata for this Reorderable
+ */
abstract getPlace(): number;
+
+ /**
+ * Update the Reorderable
+ */
+ update(): void {
+ this.oldIndex = this.newIndex;
+ }
+
+ /**
+ * Returns true if the oldIndex of this Reorderable
+ * differs from the newIndex
+ */
+ get hasMoved(): boolean {
+ return this.oldIndex !== this.newIndex
+ }
+}
+
+/**
+ * A Reorderable representation of a FormFieldMetadataValue
+ */
+export class ReorderableFormFieldMetadataValue extends Reorderable {
+
+ constructor(
+ public metadataValue: FormFieldMetadataValueObject,
+ public model: DynamicConcatModel,
+ public control: FormControl,
+ public group: DynamicFormArrayGroupModel,
+ oldIndex?: number,
+ newIndex?: number
+ ) {
+ super(oldIndex, newIndex);
+ this.metadataValue = metadataValue;
+ }
+
+ /**
+ * Return the id for this Reorderable
+ */
+ getId(): string {
+ if (hasValue(this.metadataValue.authority)) {
+ return this.metadataValue.authority;
+ } else {
+ // can't use UUIDs, they're generated client side
+ return this.metadataValue.value;
+ }
+ }
+
+ /**
+ * Return the place metadata for this Reorderable
+ */
+ getPlace(): number {
+ return this.metadataValue.place;
+ }
+
}
/**
* Represents a single relationship that can be reordered in a list of multiple relationships
*/
export class ReorderableRelationship extends Reorderable {
- relationship: Relationship;
- useLeftItem: boolean;
- constructor(relationship: Relationship, useLeftItem: boolean, oldIndex?: number, newIndex?: number) {
+ constructor(
+ public relationship: Relationship,
+ public useLeftItem: boolean,
+ protected relationshipService: RelationshipService,
+ protected store: Store,
+ protected submissionID: string,
+ oldIndex?: number,
+ newIndex?: number) {
super(oldIndex, newIndex);
this.relationship = relationship;
this.useLeftItem = useLeftItem;
}
+ /**
+ * Return the id for this Reorderable
+ */
getId(): string {
return this.relationship.id;
}
+ /**
+ * Return the place metadata for this Reorderable
+ */
getPlace(): number {
if (this.useLeftItem) {
return this.relationship.rightPlace
@@ -62,15 +138,16 @@ export class ReorderableRelationship extends Reorderable {
templateUrl: './existing-metadata-list-element.component.html',
styleUrls: ['./existing-metadata-list-element.component.scss']
})
-export class ExistingMetadataListElementComponent implements OnChanges, OnDestroy {
+export class ExistingMetadataListElementComponent implements OnInit, OnChanges, OnDestroy {
@Input() listId: string;
@Input() submissionItem: Item;
@Input() reoRel: ReorderableRelationship;
@Input() metadataFields: string[];
@Input() relationshipOptions: RelationshipOptions;
- metadataRepresentation: MetadataRepresentation;
+ @Input() submissionId: string;
+ metadataRepresentation$: BehaviorSubject = new BehaviorSubject(undefined);
relatedItem: Item;
-
+ @Output() remove: EventEmitter = new EventEmitter();
/**
* List of subscriptions to unsubscribe from
*/
@@ -82,24 +159,35 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro
) {
}
+ ngOnInit(): void {
+ this.ngOnChanges();
+ }
+
+ /**
+ * Change callback for the component
+ */
ngOnChanges() {
- const item$ = this.reoRel.useLeftItem ?
- this.reoRel.relationship.leftItem : this.reoRel.relationship.rightItem;
- this.subs.push(item$.pipe(
- getAllSucceededRemoteData(),
- getRemoteDataPayload(),
- filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
- ).subscribe((item: Item) => {
- this.relatedItem = item;
- const relationMD: MetadataValue = this.submissionItem.firstMetadata(this.relationshipOptions.metadataField, { value: this.relatedItem.uuid });
- if (hasValue(relationMD)) {
- const metadataRepresentationMD: MetadataValue = this.submissionItem.firstMetadata(this.metadataFields, { authority: relationMD.authority });
- this.metadataRepresentation = Object.assign(
- new ItemMetadataRepresentation(metadataRepresentationMD),
- this.relatedItem
- )
- }
- }));
+ if (hasValue(this.reoRel)) {
+ const item$ = this.reoRel.useLeftItem ?
+ this.reoRel.relationship.leftItem : this.reoRel.relationship.rightItem;
+ this.subs.push(item$.pipe(
+ getAllSucceededRemoteData(),
+ getRemoteDataPayload(),
+ filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
+ ).subscribe((item: Item) => {
+ this.relatedItem = item;
+ const relationMD: MetadataValue = this.submissionItem.firstMetadata(this.relationshipOptions.metadataField, { value: this.relatedItem.uuid });
+ if (hasValue(relationMD)) {
+ const metadataRepresentationMD: MetadataValue = this.submissionItem.firstMetadata(this.metadataFields, { authority: relationMD.authority });
+
+ const nextValue = Object.assign(
+ new ItemMetadataRepresentation(metadataRepresentationMD),
+ this.relatedItem
+ );
+ this.metadataRepresentation$.next(nextValue);
+ }
+ }));
+ }
}
/**
@@ -107,7 +195,8 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro
*/
removeSelection() {
this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem }));
- this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType))
+ this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId));
+ this.remove.emit();
}
/**
@@ -120,4 +209,5 @@ export class ExistingMetadataListElementComponent implements OnChanges, OnDestro
}
}
+
// tslint:enable:max-classes-per-file
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html
new file mode 100644
index 0000000000..15087d2553
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.scss
new file mode 100644
index 0000000000..ab63e324bd
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.scss
@@ -0,0 +1,3 @@
+span.text-contents{
+ padding: $btn-padding-y 0;
+}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts
new file mode 100644
index 0000000000..6b6c518bb0
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts
@@ -0,0 +1,100 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ExistingRelationListElementComponent } from './existing-relation-list-element.component';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
+import { select, Store } from '@ngrx/store';
+import { Item } from '../../../../../core/shared/item.model';
+import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
+import { RelationshipOptions } from '../../models/relationship-options.model';
+import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
+import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
+import { of as observableOf } from 'rxjs';
+import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils';
+
+describe('ExistingRelationListElementComponent', () => {
+ let component: ExistingRelationListElementComponent;
+ let fixture: ComponentFixture;
+ let selectionService;
+ let store;
+ let listID;
+ let submissionItem;
+ let relationship;
+ let reoRel;
+ let metadataFields;
+ let relationshipOptions;
+ let uuid1;
+ let uuid2;
+ let relatedItem;
+ let leftItemRD$;
+ let rightItemRD$;
+ let relatedSearchResult;
+ let submissionId;
+ let relationshipService;
+
+ function init() {
+ uuid1 = '91ce578d-2e63-4093-8c73-3faafd716000';
+ uuid2 = '0e9dba1c-e1c3-4e05-a539-446f08ef57a7';
+ selectionService = jasmine.createSpyObj('selectionService', ['deselectSingle']);
+ store = jasmine.createSpyObj('store', ['dispatch']);
+ listID = '1234-listID';
+ submissionItem = Object.assign(new Item(), { uuid: uuid1 });
+ metadataFields = ['dc.contributor.author'];
+ relationshipOptions = Object.assign(new RelationshipOptions(), { relationshipType: 'isPublicationOfAuthor', filter: 'test.filter', searchConfiguration: 'personConfiguration', nameVariants: true })
+ relatedItem = Object.assign(new Item(), { uuid: uuid2 });
+ leftItemRD$ = createSuccessfulRemoteDataObject$(relatedItem);
+ rightItemRD$ = createSuccessfulRemoteDataObject$(submissionItem);
+ relatedSearchResult = Object.assign(new ItemSearchResult(), { indexableObject: relatedItem });
+ relationshipService = {
+ updatePlace:() => observableOf({})
+ } as any;
+
+ relationship = Object.assign(new Relationship(), { leftItem: leftItemRD$, rightItem: rightItemRD$ });
+ submissionId = '1234';
+ reoRel = new ReorderableRelationship(relationship, true, relationshipService, {} as any, submissionId);
+ }
+
+ beforeEach(async(() => {
+ init();
+ TestBed.configureTestingModule({
+ declarations: [ExistingRelationListElementComponent],
+ providers: [
+ { provide: SelectableListService, useValue: selectionService },
+ { provide: Store, useValue: store },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ExistingRelationListElementComponent);
+ component = fixture.componentInstance;
+ component.listId = listID;
+ component.submissionItem = submissionItem;
+ component.reoRel = reoRel;
+ component.metadataFields = metadataFields;
+ component.relationshipOptions = relationshipOptions;
+ component.submissionId = submissionId;
+ fixture.detectChanges();
+ component.ngOnChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('removeSelection', () => {
+ it('should deselect the object in the selectable list service', () => {
+ component.removeSelection();
+ expect(selectionService.deselectSingle).toHaveBeenCalledWith(listID, relatedSearchResult);
+ });
+
+ it('should dispatch a RemoveRelationshipAction', () => {
+ component.removeSelection();
+ const action = new RemoveRelationshipAction(submissionItem, relatedItem, relationshipOptions.relationshipType, submissionId);
+ expect(store.dispatch).toHaveBeenCalledWith(action);
+ });
+ })
+});
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts
new file mode 100644
index 0000000000..65b3730773
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts
@@ -0,0 +1,120 @@
+import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { BehaviorSubject, Observable, Subscription } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { AppState } from '../../../../../app.reducer';
+import { Item } from '../../../../../core/shared/item.model';
+import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators';
+import { hasValue, isNotEmpty } from '../../../../empty.util';
+import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
+import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
+import { RelationshipOptions } from '../../models/relationship-options.model';
+import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component';
+
+// tslint:disable:max-classes-per-file
+/**
+ * Abstract class that defines objects that can be reordered
+ */
+export abstract class Reorderable {
+
+ constructor(public oldIndex?: number, public newIndex?: number) {
+ }
+
+ /**
+ * Return the id for this Reorderable
+ */
+ abstract getId(): string;
+
+ /**
+ * Return the place metadata for this Reorderable
+ */
+ abstract getPlace(): number;
+
+ /**
+ * Update the Reorderable
+ */
+ abstract update(): Observable;
+
+ /**
+ * Returns true if the oldIndex of this Reorderable
+ * differs from the newIndex
+ */
+ get hasMoved(): boolean {
+ return this.oldIndex !== this.newIndex
+ }
+}
+
+/**
+ * Represents a single existing relationship value as metadata in submission
+ */
+@Component({
+ selector: 'ds-existing-relation-list-element',
+ templateUrl: './existing-relation-list-element.component.html',
+ styleUrls: ['./existing-relation-list-element.component.scss']
+})
+export class ExistingRelationListElementComponent implements OnInit, OnChanges, OnDestroy {
+ @Input() listId: string;
+ @Input() submissionItem: Item;
+ @Input() reoRel: ReorderableRelationship;
+ @Input() metadataFields: string[];
+ @Input() relationshipOptions: RelationshipOptions;
+ @Input() submissionId: string;
+ relatedItem$: BehaviorSubject- = new BehaviorSubject
- (undefined);
+ viewType = ViewMode.ListElement;
+ @Output() remove: EventEmitter
= new EventEmitter();
+
+ /**
+ * List of subscriptions to unsubscribe from
+ */
+ private subs: Subscription[] = [];
+
+ constructor(
+ private selectableListService: SelectableListService,
+ private store: Store
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.ngOnChanges();
+ }
+
+ /**
+ * Change callback for the component
+ */
+ ngOnChanges() {
+ if (hasValue(this.reoRel)) {
+ const item$ = this.reoRel.useLeftItem ?
+ this.reoRel.relationship.leftItem : this.reoRel.relationship.rightItem;
+ this.subs.push(item$.pipe(
+ getAllSucceededRemoteData(),
+ getRemoteDataPayload(),
+ filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
+ ).subscribe((item: Item) => {
+ this.relatedItem$.next(item);
+ }));
+ }
+
+ }
+
+ /**
+ * Removes the selected relationship from the list
+ */
+ removeSelection() {
+ this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem$.getValue() }));
+ this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem$.getValue(), this.relationshipOptions.relationshipType, this.submissionId));
+ }
+
+ /**
+ * Unsubscribe from all subscriptions
+ */
+ ngOnDestroy(): void {
+ this.subs
+ .filter((sub) => hasValue(sub))
+ .forEach((sub) => sub.unsubscribe());
+ }
+
+}
+
+// tslint:enable:max-classes-per-file
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
index 75c27b6ca5..ac5ece93d1 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
@@ -1,32 +1,45 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
0}" cdkDrag cdkDragHandle>
+
0}"
+ >
+ 0">
+
+
+
+
+
+
+
-
-
+
+
+
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss
new file mode 100644
index 0000000000..b61bb9232b
--- /dev/null
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss
@@ -0,0 +1,52 @@
+@import './../../../../../../../styles/variables';
+
+:host {
+ display: block;
+}
+
+.cdk-drag {
+ margin-left: -(2 * $spacer);
+ margin-right: -(0.5 * $spacer);
+ padding-right: (0.5 * $spacer);
+ .drag-icon {
+ visibility: hidden;
+ width: (2 * $spacer);
+ color: $gray-600;
+ margin: $btn-padding-y 0;
+ line-height: $btn-line-height;
+ text-indent: 0.5 * $spacer
+ }
+
+ &:hover, &:focus {
+ cursor: grab;
+ .drag-icon {
+ visibility: visible;
+ }
+ }
+
+}
+
+.cdk-drop-list-dragging {
+ .cdk-drag {
+ cursor: grabbing;
+ .drag-icon {
+ visibility: hidden;
+ }
+ }
+}
+
+.cdk-drag-preview {
+ background-color: white;
+ border-radius: $border-radius-sm;
+ margin-left: 0;
+ box-shadow: 0 5px 5px 0px rgba(0, 0, 0, 0.2),
+ 0 8px 10px 1px rgba(0, 0, 0, 0.14),
+ 0 3px 14px 2px rgba(0, 0, 0, 0.12);
+ .drag-icon {
+ visibility: visible;
+ }
+}
+
+.cdk-drag-placeholder {
+ opacity: 0;
+}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
index 1e8fd3b55e..ea6455a138 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
@@ -1,25 +1,31 @@
+import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { Component, EventEmitter, Input, Output, QueryList } from '@angular/core';
import { FormGroup } from '@angular/forms';
import {
DynamicFormArrayComponent,
- DynamicFormArrayModel,
- DynamicFormControlCustomEvent, DynamicFormControlEvent,
+ DynamicFormControlCustomEvent,
+ DynamicFormControlEvent,
+ DynamicFormControlEventType,
DynamicFormLayout,
DynamicFormLayoutService,
DynamicFormValidationService,
DynamicTemplateDirective
} from '@ng-dynamic-forms/core';
+import { Relationship } from '../../../../../../core/shared/item-relationships/relationship.model';
+import { DynamicRowArrayModel } from '../ds-dynamic-row-array-model';
+import { hasValue } from '../../../../../empty.util';
@Component({
- selector: 'ds-dynamic-form-array',
- templateUrl: './dynamic-form-array.component.html'
+ selector: 'ds-dynamic-form-array',
+ templateUrl: './dynamic-form-array.component.html',
+ styleUrls: ['./dynamic-form-array.component.scss']
})
export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
@Input() bindId = true;
@Input() group: FormGroup;
@Input() layout: DynamicFormLayout;
- @Input() model: DynamicFormArrayModel;
+ @Input() model: DynamicRowArrayModel;
@Input() templates: QueryList
| undefined;
/* tslint:disable:no-output-rename */
@@ -27,12 +33,39 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
@Output('dfChange') change: EventEmitter = new EventEmitter();
@Output('dfFocus') focus: EventEmitter = new EventEmitter();
@Output('ngbEvent') customEvent: EventEmitter = new EventEmitter();
+
/* tslint:enable:no-output-rename */
constructor(protected layoutService: DynamicFormLayoutService,
- protected validationService: DynamicFormValidationService) {
-
+ protected validationService: DynamicFormValidationService,
+ ) {
super(layoutService, validationService);
}
+ moveSelection(event: CdkDragDrop) {
+ this.model.moveGroup(event.previousIndex, event.currentIndex - event.previousIndex);
+ const prevIndex = event.previousIndex - 1;
+ const index = event.currentIndex - 1;
+
+ if (hasValue(this.model.groups[index]) && hasValue((this.control as any).controls[index])) {
+ const $event = {
+ $event: { previousIndex: prevIndex },
+ context: { index },
+ control: (this.control as any).controls[index],
+ group: this.group,
+ model: this.model.groups[index].group[0],
+ type: DynamicFormControlEventType.Change
+ };
+
+ this.onChange($event);
+ }
+ }
+
+ update(event: any, index: number) {
+ const $event = Object.assign({}, event, {
+ context: { index: index - 1}
+ });
+
+ this.onChange($event)
+ }
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.model.ts
index 995fcbf350..7f7c3e68d5 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.model.ts
@@ -17,6 +17,7 @@ export class DynamicDsDatePickerModel extends DynamicDateControlModel {
valueUpdates: Subject;
malformedDate: boolean;
hasLanguages = false;
+ repeatable = false;
constructor(config: DynamicDateControlModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts
index 8e0c6fc20e..3aaff1339f 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component.spec.ts
@@ -15,7 +15,7 @@ describe('DsDynamicDisabledComponent', () => {
let model;
function init() {
- model = new DynamicDisabledModel({ value: 'test', repeatable: false, metadataFields: [], submissionId: '1234', id: '1' });
+ model = new DynamicDisabledModel({ value: 'test', repeatable: false, metadataFields: [], submissionId: '1234', id: '1', hasSelectableMetadata: false });
}
beforeEach(async(() => {
@@ -52,7 +52,6 @@ describe('DsDynamicDisabledComponent', () => {
it('should have a disabled input', () => {
const input = de.query(By.css('input'));
- console.log(input.nativeElement.getAttribute('disabled'));
expect(input.nativeElement.getAttribute('disabled')).toEqual('');
});
});
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
index 0fa2b3e5ed..5eb9aa8dd2 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.model.ts
@@ -5,6 +5,7 @@ export const DYNAMIC_FORM_CONTROL_TYPE_DISABLED = 'EMPTY';
export interface DsDynamicDisabledModelConfig extends DsDynamicInputModelConfig {
value?: any;
+ hasSelectableMetadata: boolean;
}
/**
@@ -14,11 +15,14 @@ export class DynamicDisabledModel extends DsDynamicInputModel {
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_DISABLED;
@serializable() value: any;
+ @serializable() hasSelectableMetadata: boolean;
constructor(config: DsDynamicDisabledModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
this.readOnly = true;
this.disabled = true;
+ this.hasSelectableMetadata = config.hasSelectableMetadata;
+
this.valueUpdates.next(config.value);
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
index af05d5bf35..7d4b58c95d 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
@@ -2,10 +2,11 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelC
import { Subject } from 'rxjs';
-import { isNotEmpty } from '../../../../empty.util';
+import { hasNoValue, isNotEmpty } from '../../../../empty.util';
import { DsDynamicInputModel } from './ds-dynamic-input.model';
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
import { RelationshipOptions } from '../../models/relationship-options.model';
+import { MetadataValue } from '../../../../../core/shared/metadata.models';
export const CONCAT_GROUP_SUFFIX = '_CONCAT_GROUP';
export const CONCAT_FIRST_INPUT_SUFFIX = '_CONCAT_FIRST_INPUT';
@@ -14,11 +15,14 @@ export const CONCAT_SECOND_INPUT_SUFFIX = '_CONCAT_SECOND_INPUT';
export interface DynamicConcatModelConfig extends DynamicFormGroupModelConfig {
separator: string;
value?: any;
+ hint?: string;
relationship?: RelationshipOptions;
repeatable: boolean;
required: boolean;
metadataFields: string[];
submissionId: string;
+ hasSelectableMetadata: boolean;
+ metadataValue?: MetadataValue;
}
export class DynamicConcatModel extends DynamicFormGroupModel {
@@ -28,8 +32,11 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
@serializable() relationship?: RelationshipOptions;
@serializable() repeatable?: boolean;
@serializable() required?: boolean;
+ @serializable() hint?: string;
@serializable() metadataFields: string[];
@serializable() submissionId: string;
+ @serializable() hasSelectableMetadata: boolean;
+ @serializable() metadataValue: MetadataValue;
isCustomGroup = true;
valueUpdates: Subject;
@@ -37,26 +44,30 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
-
this.separator = config.separator + ' ';
this.relationship = config.relationship;
this.repeatable = config.repeatable;
this.required = config.required;
+ this.hint = config.hint;
this.metadataFields = config.metadataFields;
this.submissionId = config.submissionId;
-
+ this.hasSelectableMetadata = config.hasSelectableMetadata;
+ this.metadataValue = config.metadataValue;
this.valueUpdates = new Subject();
this.valueUpdates.subscribe((value: string) => this.value = value);
}
get value() {
- const firstValue = (this.get(0) as DsDynamicInputModel).value;
- const secondValue = (this.get(1) as DsDynamicInputModel).value;
-
- if (isNotEmpty(firstValue) && isNotEmpty(secondValue)) {
- return new FormFieldMetadataValueObject(firstValue + this.separator + secondValue);
- } else if (isNotEmpty(firstValue)) {
- return new FormFieldMetadataValueObject(firstValue);
+ const [firstValue, secondValue] = this.group.map((inputModel: DsDynamicInputModel) =>
+ (typeof inputModel.value === 'string') ?
+ Object.assign(new FormFieldMetadataValueObject(), { value: inputModel.value, display: inputModel.value }) :
+ (inputModel.value as any));
+ if (isNotEmpty(firstValue) && isNotEmpty(firstValue.value) && isNotEmpty(secondValue) && isNotEmpty(secondValue.value)) {
+ return Object.assign(new FormFieldMetadataValueObject(), firstValue, { value: firstValue.value + this.separator + secondValue.value });
+ } else if (isNotEmpty(firstValue) && isNotEmpty(firstValue.value)) {
+ return Object.assign(new FormFieldMetadataValueObject(), firstValue);
+ } else if (isNotEmpty(secondValue) && isNotEmpty(secondValue.value)) {
+ return Object.assign(new FormFieldMetadataValueObject(), secondValue);
} else {
return null;
}
@@ -71,18 +82,21 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
} else {
tempValue = value.value;
}
-
- if (tempValue.includes(this.separator)) {
- values = tempValue.split(this.separator);
- } else {
- values = [tempValue, null];
+ if (hasNoValue(tempValue)) {
+ tempValue = '';
}
+ values = [...tempValue.split(this.separator), null].map((v) =>
+ Object.assign(new FormFieldMetadataValueObject(), value, { display: v, value: v }));
- if (values[0]) {
+ if (values[0].value) {
(this.get(0) as DsDynamicInputModel).valueUpdates.next(values[0]);
+ } else {
+ (this.get(0) as DsDynamicInputModel).valueUpdates.next(undefined);
}
- if (values[1]) {
+ if (values[1].value) {
(this.get(1) as DsDynamicInputModel).valueUpdates.next(values[1]);
+ } else {
+ (this.get(1) as DsDynamicInputModel).valueUpdates.next(undefined);
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
index 3827df7be6..7573b67912 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
@@ -6,16 +6,21 @@ import { AuthorityOptions } from '../../../../../core/integration/models/authori
import { hasValue } from '../../../../empty.util';
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
import { RelationshipOptions } from '../../models/relationship-options.model';
+import { MetadataValue } from '../../../../../core/shared/metadata.models';
export interface DsDynamicInputModelConfig extends DynamicInputModelConfig {
authorityOptions?: AuthorityOptions;
languageCodes?: LanguageCode[];
language?: string;
+ place?: number;
value?: any;
relationship?: RelationshipOptions;
repeatable: boolean;
metadataFields: string[];
submissionId: string;
+ hasSelectableMetadata: boolean;
+ metadataValue?: MetadataValue;
+
}
export class DsDynamicInputModel extends DynamicInputModel {
@@ -28,6 +33,8 @@ export class DsDynamicInputModel extends DynamicInputModel {
@serializable() repeatable?: boolean;
@serializable() metadataFields: string[];
@serializable() submissionId: string;
+ @serializable() hasSelectableMetadata: boolean;
+ @serializable() metadataValue: MetadataValue;
constructor(config: DsDynamicInputModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
@@ -38,6 +45,8 @@ export class DsDynamicInputModel extends DynamicInputModel {
this.value = config.value;
this.relationship = config.relationship;
this.submissionId = config.submissionId;
+ this.hasSelectableMetadata = config.hasSelectableMetadata;
+ this.metadataValue = config.metadataValue;
this.language = config.language;
if (!this.language) {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
index 7de319bf56..8925d8fd87 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
@@ -1,18 +1,34 @@
import { DynamicFormArrayModel, DynamicFormArrayModelConfig, DynamicFormControlLayout, serializable } from '@ng-dynamic-forms/core';
+import { RelationshipOptions } from '../../models/relationship-options.model';
export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig {
notRepeatable: boolean;
required: boolean;
+ submissionId: string;
+ relationshipConfig: RelationshipOptions;
+ metadataKey: string;
+ metadataFields: string[];
+ hasSelectableMetadata: boolean;
}
export class DynamicRowArrayModel extends DynamicFormArrayModel {
@serializable() notRepeatable = false;
@serializable() required = false;
+ @serializable() submissionId: string;
+ @serializable() relationshipConfig: RelationshipOptions;
+ @serializable() metadataKey: string;
+ @serializable() metadataFields: string[];
+ @serializable() hasSelectableMetadata: boolean;
isRowArray = true;
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
this.notRepeatable = config.notRepeatable;
this.required = config.required;
+ this.submissionId = config.submissionId;
+ this.relationshipConfig = config.relationshipConfig;
+ this.metadataKey = config.metadataKey;
+ this.metadataFields = config.metadataFields;
+ this.hasSelectableMetadata = config.hasSelectableMetadata;
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/form-group/dynamic-form-group.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/form-group/dynamic-form-group.component.html
index 897ea4c5e3..c80b0d4e08 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/form-group/dynamic-form-group.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/form-group/dynamic-form-group.component.html
@@ -6,7 +6,6 @@
[ngClass]="getClass('element','control')">
-
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts
index c77aabfeed..b11aa2cb20 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts
@@ -11,7 +11,7 @@ import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstr
import { AuthorityService } from '../../../../../../core/integration/authority.service';
import { AuthorityServiceStub } from '../../../../../testing/authority-service.stub';
import { DsDynamicLookupComponent } from './dynamic-lookup.component';
-import { DynamicLookupModel } from './dynamic-lookup.model';
+import { DynamicLookupModel, DynamicLookupModelConfig } from './dynamic-lookup.model';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { TranslateModule } from '@ngx-translate/core';
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
@@ -22,7 +22,7 @@ import { DynamicLookupNameModel } from './dynamic-lookup-name.model';
import { AuthorityConfidenceStateDirective } from '../../../../../authority-confidence/authority-confidence-state.directive';
import { ObjNgFor } from '../../../../../utils/object-ngfor.pipe';
-let LOOKUP_TEST_MODEL_CONFIG = {
+let LOOKUP_TEST_MODEL_CONFIG: DynamicLookupModelConfig = {
authorityOptions: {
closed: false,
metadata: 'lookup',
@@ -39,11 +39,11 @@ let LOOKUP_TEST_MODEL_CONFIG = {
readOnly: false,
required: true,
repeatable: true,
- separator: ',',
validators: { required: null },
value: undefined,
metadataFields: [],
- submissionId: '1234'
+ submissionId: '1234',
+ hasSelectableMetadata: false
};
let LOOKUP_NAME_TEST_MODEL_CONFIG = {
@@ -63,11 +63,11 @@ let LOOKUP_NAME_TEST_MODEL_CONFIG = {
readOnly: false,
required: true,
repeatable: true,
- separator: ',',
validators: { required: null },
value: undefined,
metadataFields: [],
- submissionId: '1234'
+ submissionId: '1234',
+ hasSelectableMetadata: false
};
let LOOKUP_TEST_GROUP = new FormGroup({
@@ -94,11 +94,11 @@ describe('Dynamic Lookup component', () => {
readOnly: false,
required: true,
repeatable: true,
- separator: ',',
validators: { required: null },
value: undefined,
metadataFields: [],
- submissionId: '1234'
+ submissionId: '1234',
+ hasSelectableMetadata: false
};
LOOKUP_NAME_TEST_MODEL_CONFIG = {
@@ -118,11 +118,11 @@ describe('Dynamic Lookup component', () => {
readOnly: false,
required: true,
repeatable: true,
- separator: ',',
validators: { required: null },
value: undefined,
metadataFields: [],
- submissionId: '1234'
+ submissionId: '1234',
+ hasSelectableMetadata: false
};
LOOKUP_TEST_GROUP = new FormGroup({
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
index bcddb52123..b5cb153db2 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
@@ -80,7 +80,8 @@ function init() {
submissionScope: undefined,
validators: { required: null },
repeatable: false,
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
} as DynamicRelationGroupModelConfig;
FORM_GROUP_TEST_GROUP = new FormGroup({
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
index cfe50def98..8cb44bc733 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component.html
@@ -1,8 +1,7 @@
+
{
let lookupRelationService;
function init() {
- relationship = { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true } as RelationshipOptions;
+ relationship = Object.assign(new RelationshipOptions(), { filter: 'filter', relationshipType: 'isAuthorOfPublication', nameVariants: true, searchConfiguration: 'personConfig' });
pSearchOptions = new PaginatedSearchOptions({});
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
index 9484631610..f851e52537 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component.ts
@@ -2,20 +2,18 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angu
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { Item } from '../../../../../../core/shared/item.model';
-import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
import { SearchResult } from '../../../../../search/search-result.model';
import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { RemoteData } from '../../../../../../core/data/remote-data';
-import { Observable, ReplaySubject } from 'rxjs';
+import { Observable } from 'rxjs';
import { RelationshipOptions } from '../../../models/relationship-options.model';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
import { SearchService } from '../../../../../../core/shared/search/search.service';
import { ActivatedRoute, Router } from '@angular/router';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
-import { hasValue, isNotEmpty } from '../../../../../empty.util';
-import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
-import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
+import { hasValue } from '../../../../../empty.util';
+import { map, startWith, switchMap, take, tap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
import { RouteService } from '../../../../../../core/services/route.service';
import { CollectionElementLinkType } from '../../../../../object-collection/collection-element-link.type';
@@ -47,6 +45,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
* The ID of the list to add/remove selected items to/from
*/
@Input() listId: string;
+ @Input() query: string;
/**
* Is the selection repeatable?
@@ -101,10 +100,10 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
/**
* The initial pagination to use
*/
- initialPagination = Object.assign(new PaginationComponentOptions(), {
- id: 'submission-relation-list',
+ initialPagination = {
+ page: 1,
pageSize: 5
- });
+ };
/**
* The type of links to display
@@ -129,10 +128,8 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
this.resetRoute();
this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
this.routeService.setParameter('configuration', this.relationship.searchConfiguration);
-
- this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
- switchMap((options) => this.lookupRelationService.getLocalResults(this.relationship, options))
+ switchMap((options) => this.lookupRelationService.getLocalResults(this.relationship, options).pipe(startWith(undefined)))
);
}
@@ -141,7 +138,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
*/
resetRoute() {
this.router.navigate([], {
- queryParams: Object.assign({}, { pageSize: this.initialPagination.pageSize }, this.route.snapshot.queryParams, { page: 1 })
+ queryParams: Object.assign({ query: this.query }, this.route.snapshot.queryParams, this.initialPagination),
});
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.html
index 46ee1727fe..cd55553f5b 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.html
@@ -8,7 +8,7 @@
{{'submission.sections.describe.relationship-lookup.selection-tab.no-selection' | translate}}
= 1">
-
{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + label | translate}}
+ {{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + relationshipType | translate}}
-
\ No newline at end of file
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
index a918b51930..b01af2e57b 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component.ts
@@ -30,9 +30,9 @@ import { createSuccessfulRemoteDataObject } from '../../../../../remote-data.uti
*/
export class DsDynamicLookupRelationSelectionTabComponent {
/**
- * The label to use to display i18n messages (describing the type of relationship)
+ * A string that describes the type of relationship
*/
- @Input() label: string;
+ @Input() relationshipType: string;
/**
* The ID of the list to add/remove selected items to/from
diff --git a/src/app/shared/form/builder/form-builder.service.spec.ts b/src/app/shared/form/builder/form-builder.service.spec.ts
index 972abb68b5..400ba0daff 100644
--- a/src/app/shared/form/builder/form-builder.service.spec.ts
+++ b/src/app/shared/form/builder/form-builder.service.spec.ts
@@ -56,10 +56,10 @@ describe('FormBuilderService test suite', () => {
let testFormConfiguration: SubmissionFormsModel;
let service: FormBuilderService;
- const submissionId = '1234';
+ const submissionId = '1234';
function testValidator() {
- return {testValidator: {valid: true}};
+ return { testValidator: { valid: true } };
}
function testAsyncValidator() {
@@ -71,10 +71,10 @@ describe('FormBuilderService test suite', () => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
providers: [
- {provide: FormBuilderService, useClass: FormBuilderService},
- {provide: DynamicFormValidationService, useValue: {}},
- {provide: NG_VALIDATORS, useValue: testValidator, multi: true},
- {provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true}
+ { provide: FormBuilderService, useClass: FormBuilderService },
+ { provide: DynamicFormValidationService, useValue: {} },
+ { provide: NG_VALIDATORS, useValue: testValidator, multi: true },
+ { provide: NG_ASYNC_VALIDATORS, useValue: testAsyncValidator, multi: true }
]
});
@@ -148,9 +148,9 @@ describe('FormBuilderService test suite', () => {
}
),
- new DynamicTextAreaModel({id: 'testTextArea'}),
+ new DynamicTextAreaModel({ id: 'testTextArea' }),
- new DynamicCheckboxModel({id: 'testCheckbox'}),
+ new DynamicCheckboxModel({ id: 'testCheckbox' }),
new DynamicFormArrayModel(
{
@@ -158,10 +158,10 @@ describe('FormBuilderService test suite', () => {
initialCount: 5,
groupFactory: () => {
return [
- new DynamicInputModel({id: 'testFormArrayGroupInput'}),
+ new DynamicInputModel({ id: 'testFormArrayGroupInput' }),
new DynamicFormArrayModel({
id: 'testNestedFormArray', groupFactory: () => [
- new DynamicInputModel({id: 'testNestedFormArrayGroupInput'})
+ new DynamicInputModel({ id: 'testNestedFormArrayGroupInput' })
]
})
];
@@ -173,37 +173,37 @@ describe('FormBuilderService test suite', () => {
{
id: 'testFormGroup',
group: [
- new DynamicInputModel({id: 'nestedTestInput'}),
- new DynamicTextAreaModel({id: 'nestedTestTextArea'})
+ new DynamicInputModel({ id: 'nestedTestInput' }),
+ new DynamicTextAreaModel({ id: 'nestedTestTextArea' })
]
}
),
- new DynamicSliderModel({id: 'testSlider'}),
+ new DynamicSliderModel({ id: 'testSlider' }),
- new DynamicSwitchModel({id: 'testSwitch'}),
+ new DynamicSwitchModel({ id: 'testSwitch' }),
- new DynamicDatePickerModel({id: 'testDatepicker', value: new Date()}),
+ new DynamicDatePickerModel({ id: 'testDatepicker', value: new Date() }),
- new DynamicFileUploadModel({id: 'testFileUpload'}),
+ new DynamicFileUploadModel({ id: 'testFileUpload' }),
- new DynamicEditorModel({id: 'testEditor'}),
+ new DynamicEditorModel({ id: 'testEditor' }),
- new DynamicTimePickerModel({id: 'testTimePicker'}),
+ new DynamicTimePickerModel({ id: 'testTimePicker' }),
- new DynamicRatingModel({id: 'testRating'}),
+ new DynamicRatingModel({ id: 'testRating' }),
- new DynamicColorPickerModel({id: 'testColorPicker'}),
+ new DynamicColorPickerModel({ id: 'testColorPicker' }),
- new DynamicTypeaheadModel({id: 'testTypeahead', repeatable: false, metadataFields: [], submissionId: '1234'}),
+ new DynamicTypeaheadModel({ id: 'testTypeahead', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
- new DynamicScrollableDropdownModel({id: 'testScrollableDropdown', authorityOptions: authorityOptions, repeatable: false, metadataFields: [], submissionId: '1234'}),
+ new DynamicScrollableDropdownModel({ id: 'testScrollableDropdown', authorityOptions: authorityOptions, repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
- new DynamicTagModel({id: 'testTag', repeatable: false, metadataFields: [], submissionId: '1234'}),
+ new DynamicTagModel({ id: 'testTag', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: false }),
- new DynamicListCheckboxGroupModel({id: 'testCheckboxList', authorityOptions: authorityOptions, repeatable: true}),
+ new DynamicListCheckboxGroupModel({ id: 'testCheckboxList', authorityOptions: authorityOptions, repeatable: true }),
- new DynamicListRadioGroupModel({id: 'testRadioList', authorityOptions: authorityOptions, repeatable: false}),
+ new DynamicListRadioGroupModel({ id: 'testRadioList', authorityOptions: authorityOptions, repeatable: false }),
new DynamicRelationGroupModel({
submissionId,
@@ -211,7 +211,7 @@ describe('FormBuilderService test suite', () => {
formConfiguration: [{
fields: [{
hints: 'Enter the name of the author.',
- input: {type: 'onebox'},
+ input: { type: 'onebox' },
label: 'Authors',
languageCodes: [],
mandatory: 'true',
@@ -221,12 +221,12 @@ describe('FormBuilderService test suite', () => {
authority: 'RPAuthority',
closed: false,
metadata: 'dc.contributor.author'
- }],
+ }]
} as FormFieldModel]
} as FormRowModel, {
fields: [{
hints: 'Enter the affiliation of the author.',
- input: {type: 'onebox'},
+ input: { type: 'onebox' },
label: 'Affiliation',
languageCodes: [],
mandatory: 'false',
@@ -244,29 +244,35 @@ describe('FormBuilderService test suite', () => {
scopeUUID: '',
submissionScope: '',
repeatable: false,
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: true
}),
- new DynamicDsDatePickerModel({id: 'testDate'}),
+ new DynamicDsDatePickerModel({ id: 'testDate' }),
- new DynamicLookupModel({id: 'testLookup', repeatable: false, metadataFields: [], submissionId: '1234'}),
+ new DynamicLookupModel({ id: 'testLookup', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: true }),
- new DynamicLookupNameModel({id: 'testLookupName', repeatable: false, metadataFields: [], submissionId: '1234'}),
+ new DynamicLookupNameModel({ id: 'testLookupName', repeatable: false, metadataFields: [], submissionId: '1234', hasSelectableMetadata: true }),
- new DynamicQualdropModel({id: 'testCombobox', readOnly: false, required: false}),
+ new DynamicQualdropModel({ id: 'testCombobox', readOnly: false, required: false }),
new DynamicRowArrayModel(
{
id: 'testFormRowArray',
initialCount: 5,
notRepeatable: false,
+ relationshipConfig: undefined,
+ submissionId: '1234',
groupFactory: () => {
return [
- new DynamicInputModel({id: 'testFormRowArrayGroupInput'})
+ new DynamicInputModel({ id: 'testFormRowArrayGroupInput' })
];
},
- required: false
- }
+ required: false,
+ metadataKey: 'dc.contributor.author',
+ metadataFields: ['dc.contributor.author'],
+ hasSelectableMetadata: true
+ },
),
];
@@ -628,7 +634,7 @@ describe('FormBuilderService test suite', () => {
it('should throw when unknown DynamicFormControlModel id is specified in JSON', () => {
- expect(() => service.fromJSON([{id: 'test'}]))
+ expect(() => service.fromJSON([{ id: 'test' }]))
.toThrow(new Error(`unknown form control model type defined on JSON object with id "test"`));
});
@@ -648,8 +654,8 @@ describe('FormBuilderService test suite', () => {
const formGroup = service.createFormGroup(testModel);
const nestedFormGroup = formGroup.controls.testFormGroup as FormGroup;
const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
- const newModel1 = new DynamicInputModel({id: 'newInput1'});
- const newModel2 = new DynamicInputModel({id: 'newInput2'});
+ const newModel1 = new DynamicInputModel({ id: 'newInput1' });
+ const newModel2 = new DynamicInputModel({ id: 'newInput2' });
service.addFormGroupControl(formGroup, testModel, newModel1);
service.addFormGroupControl(nestedFormGroup, nestedFormGroupModel, newModel2);
@@ -666,8 +672,8 @@ describe('FormBuilderService test suite', () => {
const formGroup = service.createFormGroup(testModel);
const nestedFormGroup = formGroup.controls.testFormGroup as FormGroup;
const nestedFormGroupModel = testModel[7] as DynamicFormGroupModel;
- const newModel1 = new DynamicInputModel({id: 'newInput1'});
- const newModel2 = new DynamicInputModel({id: 'newInput2'});
+ const newModel1 = new DynamicInputModel({ id: 'newInput1' });
+ const newModel2 = new DynamicInputModel({ id: 'newInput2' });
service.insertFormGroupControl(4, formGroup, testModel, newModel1);
service.insertFormGroupControl(0, nestedFormGroup, nestedFormGroupModel, newModel2);
diff --git a/src/app/shared/form/builder/form-builder.service.ts b/src/app/shared/form/builder/form-builder.service.ts
index 7e657d97d4..1ca0a2748c 100644
--- a/src/app/shared/form/builder/form-builder.service.ts
+++ b/src/app/shared/form/builder/form-builder.service.ts
@@ -226,7 +226,7 @@ export class FormBuilderService extends DynamicFormService {
}
hasArrayGroupValue(model: DynamicFormControlModel): boolean {
- return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG);
+ return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG || model.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY);
}
hasMappedGroupValue(model: DynamicFormControlModel): boolean {
@@ -283,11 +283,16 @@ export class FormBuilderService extends DynamicFormService {
return isNotEmpty(fieldModel) ? formGroup.get(this.getPath(fieldModel)) : null;
}
+ /**
+ * Note (discovered while debugging) this is not the ID as used in the form,
+ * but the first part of the path needed in a patch operation:
+ * e.g. add foo/0 -> the id is 'foo'
+ */
getId(model: DynamicPathable): string {
let tempModel: DynamicFormControlModel;
if (this.isArrayGroup(model as DynamicFormControlModel)) {
- return model.index.toString();
+ return hasValue((model as any).metadataKey) ? (model as any).metadataKey : model.index.toString();
} else if (this.isModelInCustomGroup(model as DynamicFormControlModel)) {
tempModel = (model as any).parent;
} else {
diff --git a/src/app/shared/form/builder/models/form-field-metadata-value.model.ts b/src/app/shared/form/builder/models/form-field-metadata-value.model.ts
index 45489e3618..c0bdb338e3 100644
--- a/src/app/shared/form/builder/models/form-field-metadata-value.model.ts
+++ b/src/app/shared/form/builder/models/form-field-metadata-value.model.ts
@@ -1,7 +1,7 @@
-import { isEmpty, isNotEmpty, isNotNull } from '../../../empty.util';
+import { hasValue, isEmpty, isNotEmpty, isNotNull } from '../../../empty.util';
import { ConfidenceType } from '../../../../core/integration/models/confidence-type';
import { PLACEHOLDER_PARENT_METADATA } from '../ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
-import { MetadataValueInterface } from '../../../../core/shared/metadata.models';
+import { MetadataValueInterface, VIRTUAL_METADATA_PREFIX } from '../../../../core/shared/metadata.models';
export interface OtherInformation {
[name: string]: string
@@ -64,4 +64,12 @@ export class FormFieldMetadataValueObject implements MetadataValueInterface {
hasPlaceholder() {
return this.hasValue() && this.value === PLACEHOLDER_PARENT_METADATA;
}
+
+ get isVirtual(): boolean {
+ return hasValue(this.authority) && this.authority.startsWith(VIRTUAL_METADATA_PREFIX);
+ }
+
+ toString() {
+ return this.display || this.value;
+ }
}
diff --git a/src/app/shared/form/builder/models/relationship-options.model.ts b/src/app/shared/form/builder/models/relationship-options.model.ts
index f1d3d0ae7a..031f468f25 100644
--- a/src/app/shared/form/builder/models/relationship-options.model.ts
+++ b/src/app/shared/form/builder/models/relationship-options.model.ts
@@ -1,4 +1,4 @@
-const RELATION_METADATA_PREFIX = 'relation.'
+const RELATION_METADATA_PREFIX = 'relation.';
/**
* The submission options for fields that can represent relationships
@@ -7,7 +7,7 @@ export class RelationshipOptions {
relationshipType: string;
filter: string;
searchConfiguration: string;
- nameVariants: boolean;
+ nameVariants: string;
get metadataField() {
return RELATION_METADATA_PREFIX + this.relationshipType
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/disabled-field-parser.spec.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts
index c885b737c2..9cd0421ea6 100644
--- a/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/disabled-field-parser.spec.ts
@@ -51,7 +51,7 @@ describe('DisabledFieldParser test suite', () => {
it('should set init value properly', () => {
initFormValues = {
description: [
- new FormFieldMetadataValueObject('test description'),
+ 'test description',
],
};
const expectedValue ='test description';
@@ -59,7 +59,7 @@ describe('DisabledFieldParser test suite', () => {
const parser = new DisabledFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
- expect(fieldModel.value).toEqual(expectedValue);
+ expect(fieldModel.value.value).toEqual(expectedValue);
});
});
diff --git a/src/app/shared/form/builder/parsers/disabled-field-parser.ts b/src/app/shared/form/builder/parsers/disabled-field-parser.ts
index 14d7051466..330c288fe9 100644
--- a/src/app/shared/form/builder/parsers/disabled-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/disabled-field-parser.ts
@@ -9,7 +9,7 @@ export class DisabledFieldParser extends FieldParser {
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
const emptyModelConfig: DsDynamicDisabledModelConfig = this.initModel(null, label);
- this.setValues(emptyModelConfig, fieldValue);
+ this.setValues(emptyModelConfig, fieldValue, true);
return new DynamicDisabledModel(emptyModelConfig)
}
}
diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts
index f218d442e1..6fafe9cbc1 100644
--- a/src/app/shared/form/builder/parsers/field-parser.ts
+++ b/src/app/shared/form/builder/parsers/field-parser.ts
@@ -1,13 +1,10 @@
import { Inject, InjectionToken } from '@angular/core';
-import { hasValue, isNotEmpty, isNotNull, isNotUndefined, isEmpty } from '../../../empty.util';
+import { hasValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../empty.util';
import { FormFieldModel } from '../models/form-field.model';
import { uniqueId } from 'lodash';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
-import {
- DynamicRowArrayModel,
- DynamicRowArrayModelConfig
-} from '../ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
+import { DynamicRowArrayModel, DynamicRowArrayModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { DsDynamicInputModel, DsDynamicInputModelConfig } from '../ds-dynamic-form-ui/models/ds-dynamic-input.model';
import { DynamicFormControlLayout } from '@ng-dynamic-forms/core';
import { setLayout } from './parser.utils';
@@ -39,36 +36,51 @@ export abstract class FieldParser {
&& (this.configData.input.type !== 'list')
&& (this.configData.input.type !== 'tag')
&& (this.configData.input.type !== 'group')
- && isEmpty(this.configData.selectableRelationship)
) {
let arrayCounter = 0;
let fieldArrayCounter = 0;
+ let metadataKey;
+
+ if (Array.isArray(this.configData.selectableMetadata) && this.configData.selectableMetadata.length === 1) {
+ metadataKey = this.configData.selectableMetadata[0].metadata;
+ }
const config = {
id: uniqueId() + '_array',
label: this.configData.label,
initialCount: this.getInitArrayIndex(),
notRepeatable: !this.configData.repeatable,
- required: JSON.parse( this.configData.mandatory),
+ relationshipConfig: this.configData.selectableRelationship,
+ required: JSON.parse(this.configData.mandatory),
+ submissionId: this.submissionId,
+ metadataKey,
+ metadataFields: this.getAllFieldIds(),
+ hasSelectableMetadata: isNotEmpty(this.configData.selectableMetadata),
groupFactory: () => {
let model;
if ((arrayCounter === 0)) {
model = this.modelFactory();
arrayCounter++;
} else {
- const fieldArrayOfValueLenght = this.getInitValueCount(arrayCounter - 1);
+ const fieldArrayOfValueLength = this.getInitValueCount(arrayCounter - 1);
let fieldValue = null;
- if (fieldArrayOfValueLenght > 0) {
- fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter++);
- if (fieldArrayCounter === fieldArrayOfValueLenght) {
+ if (fieldArrayOfValueLength > 0) {
+ if (fieldArrayCounter === 0) {
+ fieldValue = '';
+ } else {
+ fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter - 1);
+ }
+ fieldArrayCounter++;
+ if (fieldArrayCounter === fieldArrayOfValueLength + 1) {
fieldArrayCounter = 0;
arrayCounter++;
}
}
model = this.modelFactory(fieldValue, false);
+ model.id = `${model.id}_${fieldArrayCounter}`;
}
setLayout(model, 'element', 'host', 'col');
- if (model.hasLanguages) {
+ if (model.hasLanguages || isNotEmpty(model.relationship)) {
setLayout(model, 'grid', 'control', 'col');
}
return [model];
@@ -85,6 +97,7 @@ export abstract class FieldParser {
} else {
const model = this.modelFactory(this.getInitFieldValue());
+ model.submissionId = this.submissionId;
if (model.hasLanguages || isNotEmpty(model.relationship)) {
setLayout(model, 'grid', 'control', 'col');
}
@@ -147,9 +160,10 @@ export abstract class FieldParser {
}
protected getInitArrayIndex() {
+ let fieldCount = 0;
const fieldIds: any = this.getAllFieldIds();
if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length === 1 && this.initFormValues.hasOwnProperty(fieldIds)) {
- return this.initFormValues[fieldIds].length;
+ fieldCount = this.initFormValues[fieldIds].filter((value) => hasValue(value) && hasValue(value.value)).length;
} else if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length > 1) {
let counter = 0;
fieldIds.forEach((id) => {
@@ -157,10 +171,9 @@ export abstract class FieldParser {
counter = counter + this.initFormValues[id].length;
}
});
- return (counter === 0) ? 1 : counter;
- } else {
- return 1;
+ fieldCount = counter;
}
+ return (fieldCount === 0) ? 1 : fieldCount + 1
}
protected getFieldId(): string {
@@ -178,11 +191,11 @@ export abstract class FieldParser {
return ids;
}
} else {
- return [this.configData.selectableRelationship.relationshipType];
+ return ['relation.' + this.configData.selectableRelationship.relationshipType];
}
}
- protected initModel(id?: string, label = true, setErrors = true) {
+ protected initModel(id?: string, label = true, setErrors = true, hint = true) {
const controlModel = Object.create(null);
@@ -202,16 +215,17 @@ export abstract class FieldParser {
controlModel.relationship = Object.assign(new RelationshipOptions(), this.configData.selectableRelationship);
}
controlModel.repeatable = this.configData.repeatable;
- controlModel.metadataFields = isNotEmpty(this.configData.selectableMetadata) ? this.configData.selectableMetadata.map((metadataObject) => metadataObject.metadata) : [];
+ controlModel.metadataFields = this.getAllFieldIds() || [];
+ controlModel.hasSelectableMetadata = isNotEmpty(this.configData.selectableMetadata);
controlModel.submissionId = this.submissionId;
// Set label
this.setLabel(controlModel, label);
-
+ if (hint) {
+ controlModel.hint = this.configData.hints;
+ }
controlModel.placeholder = this.configData.label;
- controlModel.hint = this.configData.hints;
-
if (this.configData.mandatory && setErrors) {
this.markAsRequired(controlModel);
}
@@ -247,7 +261,6 @@ export abstract class FieldParser {
{},
controlModel.errorMessages,
{ pattern: 'error.validation.pattern' });
-
}
protected markAsRequired(controlModel) {
@@ -302,7 +315,9 @@ export abstract class FieldParser {
}
if (typeof fieldValue === 'object') {
+ modelConfig.metadataValue = fieldValue;
modelConfig.language = fieldValue.language;
+ modelConfig.place = fieldValue.place;
if (forceValueAsObj) {
modelConfig.value = fieldValue;
} else {
@@ -320,7 +335,6 @@ export abstract class FieldParser {
}
}
}
-
return modelConfig;
}
diff --git a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
index 1b0c637030..8b75856256 100644
--- a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
@@ -95,7 +95,7 @@ describe('NameFieldParser test suite', () => {
initFormValues = {
name: [new FormFieldMetadataValueObject('test, name')],
};
- const expectedValue = new FormFieldMetadataValueObject('test, name');
+ const expectedValue = new FormFieldMetadataValueObject('test, name', undefined, undefined, 'test');
const parser = new NameFieldParser(submissionId, field1, initFormValues, parserOptions);
diff --git a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
index ceb4e96320..4e34acb401 100644
--- a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
@@ -58,7 +58,7 @@ describe('SeriesFieldParser test suite', () => {
initFormValues = {
series: [new FormFieldMetadataValueObject('test; series')],
};
- const expectedValue = new FormFieldMetadataValueObject('test; series');
+ const expectedValue = new FormFieldMetadataValueObject('test; series', undefined, undefined, 'test');
const parser = new SeriesFieldParser(submissionId, field, initFormValues, parserOptions);
diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html
index 20fb942380..32ccc3b696 100644
--- a/src/app/shared/form/form.component.html
+++ b/src/app/shared/form/form.component.html
@@ -1,65 +1,57 @@
diff --git a/src/app/shared/form/form.component.scss b/src/app/shared/form/form.component.scss
index acdeb792ca..01cf09576f 100644
--- a/src/app/shared/form/form.component.scss
+++ b/src/app/shared/form/form.component.scss
@@ -42,8 +42,3 @@
.right-addon input {
padding-right: $spacer * 2.25;
}
-
-.ds-form-qualdrop-hint {
- top: -$spacer;
- position: relative;
-}
diff --git a/src/app/shared/form/form.component.spec.ts b/src/app/shared/form/form.component.spec.ts
index 6a8a1229d4..f7a0564191 100644
--- a/src/app/shared/form/form.component.spec.ts
+++ b/src/app/shared/form/form.component.spec.ts
@@ -92,7 +92,6 @@ function init() {
groupFactory: () => {
return [
new DynamicInputModel({
-
id: 'bootstrapArrayGroupInput',
placeholder: 'example array group input',
readOnly: false
@@ -362,7 +361,7 @@ describe('FormComponent test suite', () => {
spyOn((formComp as any).formService, 'validateAllFormFields');
- form.next(formState.testForm)
+ form.next(formState.testForm);
formFixture.detectChanges();
formComp.onSubmit();
@@ -418,7 +417,7 @@ describe('FormComponent test suite', () => {
}));
it('should dispatch FormChangeAction when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
- formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
+ formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
}));
@@ -426,7 +425,7 @@ describe('FormComponent test suite', () => {
it('should emit removeArrayItem Event when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
spyOn(formComp.removeArrayItem, 'emit');
- formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
+ formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
expect(formComp.removeArrayItem.emit).toHaveBeenCalled();
}));
diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts
index def61cb5b2..dee06c29b2 100644
--- a/src/app/shared/form/form.component.ts
+++ b/src/app/shared/form/form.component.ts
@@ -1,28 +1,18 @@
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
-import {
- ChangeDetectorRef,
- Component,
- EventEmitter,
- Input,
- OnDestroy,
- OnInit,
- Output, ViewEncapsulation
-} from '@angular/core';
+import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
-import {
- DynamicFormArrayModel,
- DynamicFormControlEvent,
- DynamicFormControlModel,
- DynamicFormGroupModel,
- DynamicFormLayout,
-} from '@ng-dynamic-forms/core';
+import { DynamicFormArrayModel, DynamicFormControlEvent, DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, } from '@ng-dynamic-forms/core';
import { findIndex } from 'lodash';
import { FormBuilderService } from './builder/form-builder.service';
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 { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
+import { QUALDROP_GROUP_SUFFIX } from './builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
+
+const QUALDROP_GROUP_REGEX = new RegExp(`${QUALDROP_GROUP_SUFFIX}_\\d+$`);
/**
* The default form component.
@@ -304,15 +294,49 @@ export class FormComponent implements OnDestroy, OnInit {
removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
- this.removeArrayItem.emit(this.getEvent($event, arrayContext, index, 'remove'));
+ this.removeArrayItem.emit(this.getEvent($event, arrayContext, index - 1, 'remove'));
this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext);
this.formService.changeForm(this.formId, this.formModel);
}
insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
- this.formBuilderService.insertFormArrayGroup(index, formArrayControl, arrayContext);
- this.addArrayItem.emit(this.getEvent($event, arrayContext, index, 'add'));
+
+ // 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);
+
+ // set that field to the new value
+ const model = arrayContext.groups[arrayContext.groups.length - 1].group[0] as any;
+ if (model.type === DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN) {
+ model.value = Object.values(value)[0];
+ } else if (this.formBuilderService.isQualdropGroup(model)) {
+ const ctrl = formArrayControl.controls[formArrayControl.length - 1];
+ const ctrlKey = Object.keys(ctrl.value).find((key: string) => isNotEmpty(key.match(QUALDROP_GROUP_REGEX)));
+ const valueKey = Object.keys(value).find((key: string) => isNotEmpty(key.match(QUALDROP_GROUP_REGEX)));
+ if (ctrlKey !== valueKey) {
+ Object.defineProperty(value, ctrlKey, Object.getOwnPropertyDescriptor(value, valueKey));
+ delete value[valueKey];
+ }
+ ctrl.setValue(value);
+ } else {
+ formArrayControl.controls[formArrayControl.length - 1].setValue(value);
+ }
+
+ // 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/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts
index 86bede6789..c091534f62 100644
--- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts
+++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts
@@ -5,6 +5,7 @@ import { Context } from '../../core/shared/context.model';
import { GenericConstructor } from '../../core/shared/generic-constructor';
import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component';
import { MetadataRepresentationDirective } from './metadata-representation.directive';
+import { hasValue } from '../empty.util';
@Component({
selector: 'ds-metadata-representation-loader',
@@ -15,10 +16,21 @@ import { MetadataRepresentationDirective } from './metadata-representation.direc
* Component for determining what component to use depending on the item's relationship type (relationship.type), its metadata representation and, optionally, its context
*/
export class MetadataRepresentationLoaderComponent implements OnInit {
+ private componentRefInstance: MetadataRepresentationListElementComponent;
+
/**
* The item or metadata to determine the component for
*/
- @Input() mdRepresentation: MetadataRepresentation;
+ private _mdRepresentation: MetadataRepresentation;
+ get mdRepresentation(): MetadataRepresentation {
+ return this._mdRepresentation;
+ }
+ @Input() set mdRepresentation(nextValue: MetadataRepresentation) {
+ this._mdRepresentation = nextValue;
+ if (hasValue(this.componentRefInstance)) {
+ this.componentRefInstance.metadataRepresentation = nextValue;
+ }
+ }
/**
* The optional context
@@ -43,7 +55,8 @@ export class MetadataRepresentationLoaderComponent implements OnInit {
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
- (componentRef.instance as MetadataRepresentationListElementComponent).metadataRepresentation = this.mdRepresentation;
+ this.componentRefInstance = componentRef.instance as MetadataRepresentationListElementComponent;
+ this.componentRefInstance.metadataRepresentation = this.mdRepresentation;
}
/**
diff --git a/src/app/shared/mocks/form-models.mock.ts b/src/app/shared/mocks/form-models.mock.ts
index e4f9ec3131..cd6228417b 100644
--- a/src/app/shared/mocks/form-models.mock.ts
+++ b/src/app/shared/mocks/form-models.mock.ts
@@ -10,7 +10,6 @@ import { AuthorityValue } from '../../core/integration/models/authority.value';
import { FormFieldMetadataValueObject } from '../form/builder/models/form-field-metadata-value.model';
import { DynamicRowGroupModel } from '../form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { FormRowModel } from '../../core/config/models/config-submission-form.model';
-import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
export const qualdropSelectConfig = {
name: 'dc.identifier_QUALDROP_METADATA',
@@ -56,7 +55,8 @@ export const qualdropInputConfig = {
repeatable: false,
value: 'test',
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockQualdropSelectModel = new DynamicSelectModel(qualdropSelectConfig);
@@ -76,10 +76,15 @@ const rowArrayQualdropConfig = {
id: 'row_QUALDROP_GROUP',
initialCount: 1,
notRepeatable: true,
+ relationshipConfig: undefined,
groupFactory: () => {
return [MockQualdropModel];
},
- required: false
+ required: false,
+ submissionId: '1234',
+ metadataKey: 'dc.some.key',
+ metadataFields: ['dc.some.key'],
+ hasSelectableMetadata: false
} as DynamicRowArrayModelConfig;
export const MockRowArrayQualdropModel: DynamicRowArrayModel = new DynamicRowArrayModel(rowArrayQualdropConfig);
@@ -136,7 +141,8 @@ const relationGroupConfig = {
'issue test 2'
],
},
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const MockRelationModel: DynamicRelationGroupModel = new DynamicRelationGroupModel(relationGroupConfig);
@@ -165,7 +171,8 @@ export const inputWithLanguageAndAuthorityConfig = {
id: 'testWithLanguageAndAuthority',
},
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithLanguageAndAuthorityModel = new DsDynamicInputModel(inputWithLanguageAndAuthorityConfig);
@@ -189,7 +196,8 @@ export const inputWithLanguageConfig = {
repeatable: false,
value: 'testWithLanguage',
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithLanguageModel = new DsDynamicInputModel(inputWithLanguageConfig);
@@ -218,7 +226,8 @@ export const inputWithLanguageAndAuthorityArrayConfig = {
id: 'testLanguageAndAuthorityArray',
}],
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithLanguageAndAuthorityArrayModel = new DsDynamicInputModel(inputWithLanguageAndAuthorityArrayConfig);
@@ -231,7 +240,8 @@ export const inputWithFormFieldValueConfig = {
repeatable: false,
value: new FormFieldMetadataValueObject('testWithFormFieldValue'),
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithFormFieldValueModel = new DsDynamicInputModel(inputWithFormFieldValueConfig);
@@ -244,7 +254,8 @@ export const inputWithAuthorityValueConfig = {
repeatable: false,
value: Object.assign({}, new AuthorityValue(), { value: 'testWithAuthorityValue', id: 'testWithAuthorityValue', display: 'testWithAuthorityValue' }),
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithAuthorityValueModel = new DsDynamicInputModel(inputWithAuthorityValueConfig);
@@ -257,7 +268,8 @@ export const inputWithObjectValueConfig = {
repeatable: false,
value: { value: 'testWithObjectValue', id: 'testWithObjectValue', display: 'testWithObjectValue' },
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockInputWithObjectValueModel = new DsDynamicInputModel(inputWithObjectValueConfig);
@@ -274,7 +286,8 @@ export const fileFormEditInputConfig = {
disabled: false,
repeatable: false,
submissionId: '1234',
- metadataFields: []
+ metadataFields: [],
+ hasSelectableMetadata: false
};
export const mockFileFormEditInputModel = new DsDynamicInputModel(fileFormEditInputConfig);
diff --git a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.spec.ts b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.spec.ts
index 6e7f7bf65b..503af1238a 100644
--- a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.spec.ts
+++ b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.spec.ts
@@ -148,7 +148,7 @@ describe('WorkspaceitemActionsComponent', () => {
it('should display a success notification on delete success', async(() => {
spyOn((component as any).modalService, 'open').and.returnValue({result: Promise.resolve('ok')});
- mockDataService.delete.and.returnValue(observableOf(true));
+ mockDataService.delete.and.returnValue(observableOf({ isSuccessful: true }));
spyOn(component, 'reload');
component.confirmDiscard('ok');
diff --git a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts
index 27512d899e..e28c6cb11d 100644
--- a/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts
+++ b/src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts
@@ -11,6 +11,7 @@ import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem
import { NotificationsService } from '../../notifications/notifications.service';
import { RequestService } from '../../../core/data/request.service';
import { SearchService } from '../../../core/shared/search/search.service';
+import { RestResponse } from '../../../core/cache/response.models';
/**
* This component represents actions related to WorkspaceItem object.
@@ -63,9 +64,9 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent
{
+ .subscribe((response: RestResponse) => {
this.processingDelete$.next(false);
- this.handleActionResponse(response);
+ this.handleActionResponse(response.isSuccessful);
})
}
}
diff --git a/src/app/shared/object-collection/object-collection.component.spec.ts b/src/app/shared/object-collection/object-collection.component.spec.ts
index 7ba8328495..4d23600603 100644
--- a/src/app/shared/object-collection/object-collection.component.spec.ts
+++ b/src/app/shared/object-collection/object-collection.component.spec.ts
@@ -33,8 +33,8 @@ describe('ObjectCollectionComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(ObjectCollectionComponent);
objectCollectionComponent = fixture.componentInstance;
-
}));
+
it('should only show the grid component when the viewmode is set to grid', () => {
objectCollectionComponent.currentMode$ = observableOf(ViewMode.GridElement);
diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts
index 4e6e206ddd..255f66ac86 100644
--- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts
+++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts
@@ -46,6 +46,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
*/
@Input() listID: string;
+ /**
+ * Whether to show the badge label or not
+ */
+ @Input() showLabel = true;
+
/**
* Directive hook used to place the dynamic child component
*/
@@ -68,6 +73,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
(componentRef.instance as any).index = this.index;
(componentRef.instance as any).linkType = this.linkType;
(componentRef.instance as any).listID = this.listID;
+ (componentRef.instance as any).showLabel = this.showLabel;
+ (componentRef.instance as any).context = this.context;
+ (componentRef.instance as any).viewMode = this.viewMode;
}
/**
diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts
index 3602f45ede..402984731c 100644
--- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts
+++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts
@@ -29,6 +29,21 @@ export class AbstractListableElementComponent {
*/
@Input() index: number;
+ /**
+ * Whether to show the badge label or not
+ */
+ @Input() showLabel = true;
+
+ /**
+ * The context we matched on to get this component
+ */
+ @Input() context: Context;
+
+ /**
+ * The viewmode we matched on to get this component
+ */
+ @Input() viewMode: ViewMode;
+
/**
* The available link types
*/
diff --git a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
index 92d85d03f4..56a83913a7 100644
--- a/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
+++ b/src/app/shared/object-collection/shared/selectable-list-item-control/selectable-list-item-control.component.html
@@ -4,7 +4,7 @@
[id]="'object' + index"
[ngModel]="selected$ | async"
(ngModelChange)="selectCheckbox($event)">
-
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
index ec0b792e34..3c2d54b003 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.html
@@ -15,7 +15,7 @@