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 @@