mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #1025 from 4Science/CST-3782-fix-repeatable-fields
Cst 3782 fix repeatable fields
This commit is contained in:
@@ -11,12 +11,15 @@
|
|||||||
'd-none': value?.isVirtual && (model.hasSelectableMetadata || context?.index > 0)}">
|
'd-none': value?.isVirtual && (model.hasSelectableMetadata || context?.index > 0)}">
|
||||||
<div [ngClass]="getClass('grid', 'control')">
|
<div [ngClass]="getClass('grid', 'control')">
|
||||||
<ng-container #componentViewContainer></ng-container>
|
<ng-container #componentViewContainer></ng-container>
|
||||||
<small *ngIf="hasHint && (model.repeatable === false || context?.index === 0) && (!showErrorMessages || errorMessages.length === 0)"
|
<small *ngIf="hasHint && (model.repeatable === false || context?.index === context?.context?.groups?.length - 1) && (!showErrorMessages || errorMessages.length === 0)"
|
||||||
class="text-muted" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||||
|
<!-- In case of repeatable fields show empty space for all elements except the first -->
|
||||||
|
<div *ngIf="context?.index !== null
|
||||||
|
&& (!showErrorMessages || errorMessages.length === 0)" class="clearfix w-100 mb-2"></div>
|
||||||
|
|
||||||
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
|
<div *ngIf="showErrorMessages" [ngClass]="[getClass('element', 'errors'), getClass('grid', 'errors')]">
|
||||||
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate: model.validators }}</small>
|
<small *ngFor="let message of errorMessages" class="invalid-feedback d-block">{{ message | translate: model.validators }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" >
|
<div *ngIf="model.languageCodes && model.languageCodes.length > 0" class="col-xs-2" >
|
||||||
@@ -32,12 +35,12 @@
|
|||||||
<option *ngFor="let lang of model.languageCodes" [value]="lang.code">{{lang.display}}</option>
|
<option *ngFor="let lang of model.languageCodes" [value]="lang.code">{{lang.display}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isRelationship" class="col-auto text-center" [class.invisible]="context?.index > 0">
|
<div *ngIf="isRelationship && !isVirtual()" class="col-auto text-center">
|
||||||
<button class="btn btn-secondary"
|
<button class="btn btn-secondary"
|
||||||
type="button"
|
type="button"
|
||||||
ngbTooltip="{{'form.lookup-help' | translate}}"
|
ngbTooltip="{{'form.lookup-help' | translate}}"
|
||||||
placement="top"
|
placement="top"
|
||||||
(click)="openLookup(); $event.stopPropagation();">{{'form.lookup' | translate}}
|
(click)="openLookup(); $event.stopPropagation();"><i class="fa fa-search"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,6 +68,9 @@
|
|||||||
[relationshipOptions]="model.relationship"
|
[relationshipOptions]="model.relationship"
|
||||||
>
|
>
|
||||||
</ds-existing-relation-list-element>
|
</ds-existing-relation-list-element>
|
||||||
|
<small *ngIf="hasHint && (model.repeatable === false || context?.index === context?.context?.groups?.length - 1) && (!showErrorMessages || errorMessages.length === 0)"
|
||||||
|
class="text-muted ds-hint" [innerHTML]="model.hint | translate" [ngClass]="getClass('element', 'hint')"></small>
|
||||||
|
<div class="clearfix w-100 mb-2"></div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -37,7 +37,6 @@ import {
|
|||||||
DynamicFormControl,
|
DynamicFormControl,
|
||||||
DynamicFormControlContainerComponent,
|
DynamicFormControlContainerComponent,
|
||||||
DynamicFormControlEvent,
|
DynamicFormControlEvent,
|
||||||
DynamicFormControlEventType,
|
|
||||||
DynamicFormControlModel,
|
DynamicFormControlModel,
|
||||||
DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
DynamicFormLayoutService,
|
DynamicFormLayoutService,
|
||||||
@@ -91,10 +90,10 @@ import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-di
|
|||||||
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteData,
|
getAllSucceededRemoteData,
|
||||||
|
getFirstSucceededRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getPaginatedListPayload,
|
getPaginatedListPayload,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload
|
||||||
getFirstSucceededRemoteData
|
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
@@ -374,6 +373,15 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasRelationship() {
|
||||||
|
return isNotEmpty(this.model) && this.model.hasOwnProperty('relationship') && isNotEmpty(this.model.relationship);
|
||||||
|
}
|
||||||
|
|
||||||
|
isVirtual() {
|
||||||
|
const value: FormFieldMetadataValueObject = this.model.metadataValue;
|
||||||
|
return isNotEmpty(value) && value.isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
public hasResultsSelected(): Observable<boolean> {
|
public hasResultsSelected(): Observable<boolean> {
|
||||||
return this.model.value.pipe(map((list: SearchResult<DSpaceObject>[]) => isNotEmpty(list)));
|
return this.model.value.pipe(map((list: SearchResult<DSpaceObject>[]) => isNotEmpty(list)));
|
||||||
}
|
}
|
||||||
@@ -385,6 +393,11 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
|
||||||
size: 'lg'
|
size: 'lg'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasValue(this.model.value)) {
|
||||||
|
this.submissionService.dispatchSave(this.model.submissionId);
|
||||||
|
}
|
||||||
|
|
||||||
const modalComp = this.modalRef.componentInstance;
|
const modalComp = this.modalRef.componentInstance;
|
||||||
|
|
||||||
if (hasValue(this.model.value) && !this.model.readOnly) {
|
if (hasValue(this.model.value) && !this.model.readOnly) {
|
||||||
@@ -395,18 +408,6 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.repeatable = this.model.repeatable;
|
||||||
modalComp.listId = this.listId;
|
modalComp.listId = this.listId;
|
||||||
modalComp.relationshipOptions = this.model.relationship;
|
modalComp.relationshipOptions = this.model.relationship;
|
||||||
@@ -437,6 +438,10 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
.forEach((sub) => sub.unsubscribe());
|
.forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasHint(): boolean {
|
||||||
|
return isNotEmpty(this.model.hint) && this.model.hint !== ' ';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize this.item$ based on this.model.submissionId
|
* Initialize this.item$ based on this.model.submissionId
|
||||||
*/
|
*/
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</span>
|
</span>
|
||||||
<button type="button" class="btn btn-secondary"
|
<button type="button" class="btn btn-secondary"
|
||||||
|
title="{{'form.remove' | translate}}"
|
||||||
(click)="removeSelection()">
|
(click)="removeSelection()">
|
||||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
<i class="fas fa-trash" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -14,6 +14,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils
|
|||||||
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
||||||
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { TranslateLoaderMock } from '../../../../testing/translate-loader.mock';
|
||||||
|
|
||||||
describe('ExistingMetadataListElementComponent', () => {
|
describe('ExistingMetadataListElementComponent', () => {
|
||||||
let component: ExistingMetadataListElementComponent;
|
let component: ExistingMetadataListElementComponent;
|
||||||
@@ -65,6 +67,14 @@ describe('ExistingMetadataListElementComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
declarations: [ExistingMetadataListElementComponent],
|
declarations: [ExistingMetadataListElementComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SelectableListService, useValue: selectionService },
|
{ provide: SelectableListService, useValue: selectionService },
|
||||||
|
@@ -2,44 +2,42 @@
|
|||||||
<div [id]="id"
|
<div [id]="id"
|
||||||
[formArrayName]="model.id"
|
[formArrayName]="model.id"
|
||||||
[ngClass]="getClass('element', 'control')">
|
[ngClass]="getClass('element', 'control')">
|
||||||
<div role="group"
|
|
||||||
formGroupName="0" [ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]">
|
<!-- Draggable Container -->
|
||||||
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: model.groups[0]"></ng-container>
|
<div cdkDropList cdkDropListLockAxis="y" (cdkDropListDropped)="moveSelection($event)">
|
||||||
<ng-container *ngTemplateOutlet="controlContainer; context: {$implicit: 0}"></ng-container>
|
<!-- Draggable Items -->
|
||||||
<ng-container *ngTemplateOutlet="endTemplate?.templateRef; context: model.groups[0]"></ng-container>
|
<div *ngFor="let groupModel of model.groups; let idx = index"
|
||||||
</div>
|
role="group"
|
||||||
<div cdkDropList cdkDropListLockAxis="y" (cdkDropListDropped)="moveSelection($event)">
|
[formGroupName]="idx"
|
||||||
<div *ngFor="let groupModel of model.groups; let idx = index"
|
[ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]"
|
||||||
[ngClass]="{'pt-2 pb-2': idx > 0}" cdkDrag cdkDragHandle>
|
cdkDrag
|
||||||
<div [formGroupName]="idx"
|
cdkDragHandle
|
||||||
[class]="getClass('element', 'group') + ' ' + getClass('grid', 'group')"
|
[cdkDragDisabled]="dragDisabled"
|
||||||
[ngClass]="{'d-flex align-items-center': idx > 0}"
|
[cdkDragPreviewClass]="'ds-submission-reorder-dragging'">
|
||||||
>
|
<!-- Item content -->
|
||||||
<ng-container *ngIf="idx > 0">
|
<i class="drag-icon fas fa-grip-vertical fa-fw" [class.invisible]="dragDisabled"></i>
|
||||||
<i class="drag-icon fas fa-grip-vertical fa-fw"></i>
|
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: groupModel"></ng-container>
|
||||||
<ng-container *ngTemplateOutlet="startTemplate?.templateRef; context: groupModel"></ng-container>
|
<ds-dynamic-form-control-container *ngFor="let _model of groupModel.group"
|
||||||
<ng-container *ngTemplateOutlet="controlContainer; context: {$implicit: idx}"></ng-container>
|
[bindId]="false"
|
||||||
<ng-container *ngTemplateOutlet="endTemplate?.templateRef; context: groupModel"></ng-container>
|
[formGroup]="group"
|
||||||
</ng-container>
|
[context]="groupModel"
|
||||||
</div>
|
[group]="control.get([idx])"
|
||||||
</div>
|
[hidden]="_model.hidden"
|
||||||
|
[layout]="formLayout"
|
||||||
|
[model]="_model"
|
||||||
|
[templates]="templates"
|
||||||
|
[ngClass]="[getClass('element', 'host', _model), getClass('grid', 'host', _model)]"
|
||||||
|
(dfBlur)="onBlur($event)"
|
||||||
|
(dfChange)="onChange($event)"
|
||||||
|
(dfFocus)="onFocus($event)"
|
||||||
|
(ngbEvent)="onCustomEvent($event, null, true)"></ds-dynamic-form-control-container>
|
||||||
|
|
||||||
|
<ng-container *ngTemplateOutlet="endTemplate?.templateRef; context: groupModel"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
<ng-template #controlContainer let-idx>
|
|
||||||
<ds-dynamic-form-control-container *ngFor="let _model of model.groups[idx].group"
|
|
||||||
[bindId]="false"
|
|
||||||
[context]="model.groups[idx]"
|
|
||||||
[group]="control.get([idx])"
|
|
||||||
[hidden]="_model.hidden"
|
|
||||||
[layout]="formLayout"
|
|
||||||
[model]="_model"
|
|
||||||
[templates]="templates"
|
|
||||||
[ngClass]="[getClass('element', 'host', _model), getClass('grid', 'host', _model)]"
|
|
||||||
(dfBlur)="update($event, idx)"
|
|
||||||
(dfChange)="update($event, idx)"
|
|
||||||
(dfFocus)="onFocus($event)"
|
|
||||||
(ngbEvent)="onCustomEvent($event, null, true)"></ds-dynamic-form-control-container>
|
|
||||||
</ng-template>
|
|
||||||
|
@@ -5,48 +5,41 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cdk-drag {
|
.cdk-drag {
|
||||||
margin-left: calc(-2 * var(--bs-spacer));
|
margin-left: calc(-2.3 * var(--bs-spacer));
|
||||||
margin-right: calc(-0.5 * var(--bs-spacer));
|
margin-right: calc(-0.5 * var(--bs-spacer));
|
||||||
padding-right: calc(0.5 * var(--bs-spacer));
|
padding-right: calc(0.5 * var(--bs-spacer));
|
||||||
.drag-icon {
|
.drag-icon {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
width: calc(2 * var(--bs-spacer));
|
width: calc(2 * var(--bs-spacer));
|
||||||
color: var(--bs-gray-600);
|
color: var(--bs-gray-600);
|
||||||
margin: var(--bs-btn-padding-y) 0;
|
margin: var(--bs-btn-padding-y) 0;
|
||||||
line-height: var(--bs-btn-line-height);
|
line-height: var(--bs-btn-line-height);
|
||||||
text-indent: calc(0.5 * var(--bs-spacer))
|
text-indent: calc(0.5 * var(--bs-spacer))
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
.drag-icon {
|
.drag-icon {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cdk-drop-list-dragging {
|
.cdk-drop-list-dragging {
|
||||||
.cdk-drag {
|
.cdk-drag {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
.drag-icon {
|
.drag-icon {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cdk-drag-preview {
|
.cdk-drag-preview {
|
||||||
background-color: white;
|
margin: 0;
|
||||||
border-radius: var(--bs-border-radius-sm);
|
padding: 0;
|
||||||
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 {
|
.cdk-drag-placeholder {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
@@ -5,15 +5,15 @@ import {
|
|||||||
DynamicFormArrayComponent,
|
DynamicFormArrayComponent,
|
||||||
DynamicFormControlCustomEvent,
|
DynamicFormControlCustomEvent,
|
||||||
DynamicFormControlEvent,
|
DynamicFormControlEvent,
|
||||||
DynamicFormControlEventType,
|
DynamicFormControlLayout,
|
||||||
DynamicFormControlLayout, DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
DynamicFormLayoutService,
|
DynamicFormLayoutService,
|
||||||
DynamicFormValidationService,
|
DynamicFormValidationService,
|
||||||
DynamicTemplateDirective
|
DynamicTemplateDirective
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { Relationship } from '../../../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { DynamicRowArrayModel } from '../ds-dynamic-row-array-model';
|
|
||||||
import { hasValue } from '../../../../../empty.util';
|
import { hasValue } from '../../../../../empty.util';
|
||||||
|
import { DynamicRowArrayModel } from '../ds-dynamic-row-array-model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-form-array',
|
selector: 'ds-dynamic-form-array',
|
||||||
@@ -25,7 +25,7 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
|
|||||||
@Input() formLayout: DynamicFormLayout;
|
@Input() formLayout: DynamicFormLayout;
|
||||||
@Input() group: FormGroup;
|
@Input() group: FormGroup;
|
||||||
@Input() layout: DynamicFormControlLayout;
|
@Input() layout: DynamicFormControlLayout;
|
||||||
@Input() model: DynamicRowArrayModel;
|
@Input() model: DynamicRowArrayModel;// DynamicRow?
|
||||||
@Input() templates: QueryList<DynamicTemplateDirective> | undefined;
|
@Input() templates: QueryList<DynamicTemplateDirective> | undefined;
|
||||||
|
|
||||||
/* tslint:disable:no-output-rename */
|
/* tslint:disable:no-output-rename */
|
||||||
@@ -43,22 +43,25 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveSelection(event: CdkDragDrop<Relationship>) {
|
moveSelection(event: CdkDragDrop<Relationship>) {
|
||||||
|
|
||||||
|
// prevent propagating events generated releasing on the same position
|
||||||
|
if (event.previousIndex === event.currentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.model.moveGroup(event.previousIndex, event.currentIndex - event.previousIndex);
|
this.model.moveGroup(event.previousIndex, event.currentIndex - event.previousIndex);
|
||||||
const prevIndex = event.previousIndex - 1;
|
const prevIndex = event.previousIndex;
|
||||||
const index = event.currentIndex - 1;
|
const index = event.currentIndex;
|
||||||
|
|
||||||
if (hasValue(this.model.groups[index]) && hasValue((this.control as any).controls[index])) {
|
if (hasValue(this.model.groups[index]) && hasValue((this.control as any).controls[index])) {
|
||||||
const $event = {
|
this.onCustomEvent({
|
||||||
$event: { previousIndex: prevIndex },
|
previousIndex: prevIndex,
|
||||||
context: { index },
|
index,
|
||||||
control: (this.control as any).controls[index],
|
arrayModel: this.model,
|
||||||
group: this.group,
|
|
||||||
model: this.model.groups[index].group[0],
|
model: this.model.groups[index].group[0],
|
||||||
type: DynamicFormControlEventType.Change
|
control: (this.control as any).controls[index]
|
||||||
};
|
}, 'move');
|
||||||
|
}
|
||||||
this.onChange($event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(event: any, index: number) {
|
update(event: any, index: number) {
|
||||||
@@ -68,4 +71,11 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
|
|||||||
|
|
||||||
this.onChange($event);
|
this.onChange($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the drag feature is disabled for this DynamicRowArrayModel.
|
||||||
|
*/
|
||||||
|
get dragDisabled(): boolean {
|
||||||
|
return this.model.groups.length === 1 || !this.model.isDraggable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig
|
|||||||
metadataKey: string;
|
metadataKey: string;
|
||||||
metadataFields: string[];
|
metadataFields: string[];
|
||||||
hasSelectableMetadata: boolean;
|
hasSelectableMetadata: boolean;
|
||||||
|
isDraggable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicRowArrayModel extends DynamicFormArrayModel {
|
export class DynamicRowArrayModel extends DynamicFormArrayModel {
|
||||||
@@ -19,6 +20,7 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
|
|||||||
@serializable() metadataKey: string;
|
@serializable() metadataKey: string;
|
||||||
@serializable() metadataFields: string[];
|
@serializable() metadataFields: string[];
|
||||||
@serializable() hasSelectableMetadata: boolean;
|
@serializable() hasSelectableMetadata: boolean;
|
||||||
|
@serializable() isDraggable: boolean;
|
||||||
isRowArray = true;
|
isRowArray = true;
|
||||||
|
|
||||||
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
|
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
|
||||||
@@ -30,5 +32,6 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
|
|||||||
this.metadataKey = config.metadataKey;
|
this.metadataKey = config.metadataKey;
|
||||||
this.metadataFields = config.metadataFields;
|
this.metadataFields = config.metadataFields;
|
||||||
this.hasSelectableMetadata = config.hasSelectableMetadata;
|
this.hasSelectableMetadata = config.hasSelectableMetadata;
|
||||||
|
this.isDraggable = config.isDraggable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<ngb-tabset>
|
<ds-loading *ngIf="!item || !collection"></ds-loading>
|
||||||
|
<ngb-tabset *ngIf="item && collection">
|
||||||
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : {count: (totalInternal$ | async)}">
|
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : {count: (totalInternal$ | async)}">
|
||||||
<ng-template ngbTabContent>
|
<ng-template ngbTabContent>
|
||||||
<ds-dynamic-lookup-relation-search-tab
|
<ds-dynamic-lookup-relation-search-tab
|
||||||
|
@@ -21,6 +21,10 @@ import { createPaginatedList } from '../../../../testing/utils.test';
|
|||||||
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
||||||
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
import { LookupRelationService } from '../../../../../core/data/lookup-relation.service';
|
||||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { SubmissionService } from '../../../../../submission/submission.service';
|
||||||
|
import { SubmissionObjectDataService } from '../../../../../core/submission/submission-object-data.service';
|
||||||
|
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
|
||||||
describe('DsDynamicLookupRelationModalComponent', () => {
|
describe('DsDynamicLookupRelationModalComponent', () => {
|
||||||
let component: DsDynamicLookupRelationModalComponent;
|
let component: DsDynamicLookupRelationModalComponent;
|
||||||
@@ -28,6 +32,7 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
let item;
|
let item;
|
||||||
let item1;
|
let item1;
|
||||||
let item2;
|
let item2;
|
||||||
|
let testWSI;
|
||||||
let searchResult1;
|
let searchResult1;
|
||||||
let searchResult2;
|
let searchResult2;
|
||||||
let listID;
|
let listID;
|
||||||
@@ -41,6 +46,8 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
let lookupRelationService;
|
let lookupRelationService;
|
||||||
let rdbService;
|
let rdbService;
|
||||||
let submissionId;
|
let submissionId;
|
||||||
|
let submissionService;
|
||||||
|
let submissionObjectDataService;
|
||||||
|
|
||||||
const externalSources = [
|
const externalSources = [
|
||||||
Object.assign(new ExternalSource(), {
|
Object.assign(new ExternalSource(), {
|
||||||
@@ -56,11 +63,16 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
];
|
];
|
||||||
const totalLocal = 10;
|
const totalLocal = 10;
|
||||||
const totalExternal = 8;
|
const totalExternal = 8;
|
||||||
|
const collection: Collection = new Collection();
|
||||||
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
|
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
|
||||||
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
|
||||||
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
|
||||||
|
testWSI = new WorkspaceItem();
|
||||||
|
testWSI.item = createSuccessfulRemoteDataObject$(item);
|
||||||
|
testWSI.collection = createSuccessfulRemoteDataObject$(collection);
|
||||||
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
|
||||||
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
|
||||||
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
|
||||||
@@ -87,6 +99,12 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
aggregate: createSuccessfulRemoteDataObject$(externalSources)
|
aggregate: createSuccessfulRemoteDataObject$(externalSources)
|
||||||
});
|
});
|
||||||
|
submissionService = jasmine.createSpyObj('SubmissionService', {
|
||||||
|
dispatchSave: jasmine.createSpy('dispatchSave')
|
||||||
|
});
|
||||||
|
submissionObjectDataService = jasmine.createSpyObj('SubmissionObjectDataService', {
|
||||||
|
findById: createSuccessfulRemoteDataObject$(testWSI)
|
||||||
|
});
|
||||||
submissionId = '1234';
|
submissionId = '1234';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +129,8 @@ describe('DsDynamicLookupRelationModalComponent', () => {
|
|||||||
},
|
},
|
||||||
{ provide: RelationshipTypeService, useValue: {} },
|
{ provide: RelationshipTypeService, useValue: {} },
|
||||||
{ provide: RemoteDataBuildService, useValue: rdbService },
|
{ provide: RemoteDataBuildService, useValue: rdbService },
|
||||||
|
{ provide: SubmissionService, useValue: submissionService },
|
||||||
|
{ provide: SubmissionObjectDataService, useValue: submissionObjectDataService },
|
||||||
{
|
{
|
||||||
provide: Store, useValue: {
|
provide: Store, useValue: {
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
|
@@ -11,7 +11,11 @@ import { ListableObject } from '../../../../object-collection/shared/listable-ob
|
|||||||
import { RelationshipOptions } from '../../models/relationship-options.model';
|
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||||
import { SearchResult } from '../../../../search/search-result.model';
|
import { SearchResult } from '../../../../search/search-result.model';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
|
import {
|
||||||
|
getAllSucceededRemoteData,
|
||||||
|
getAllSucceededRemoteDataPayload,
|
||||||
|
getRemoteDataPayload
|
||||||
|
} from '../../../../../core/shared/operators';
|
||||||
import { AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipNameVariantAction } from './relationship.actions';
|
import { AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipNameVariantAction } from './relationship.actions';
|
||||||
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
||||||
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
|
||||||
@@ -23,6 +27,12 @@ import { ExternalSource } from '../../../../../core/shared/external-source.model
|
|||||||
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { followLink } from '../../../../utils/follow-link-config.model';
|
||||||
|
import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model';
|
||||||
|
import { Collection } from '../../../../../core/shared/collection.model';
|
||||||
|
import { SubmissionService } from '../../../../../submission/submission.service';
|
||||||
|
import { SubmissionObjectDataService } from '../../../../../core/submission/submission-object-data.service';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-modal',
|
selector: 'ds-dynamic-lookup-relation-modal',
|
||||||
@@ -112,6 +122,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
totalExternal$: Observable<number[]>;
|
totalExternal$: Observable<number[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions to unsubscribe from
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public modal: NgbActiveModal,
|
public modal: NgbActiveModal,
|
||||||
private selectableListService: SelectableListService,
|
private selectableListService: SelectableListService,
|
||||||
@@ -121,14 +136,17 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
private lookupRelationService: LookupRelationService,
|
private lookupRelationService: LookupRelationService,
|
||||||
private searchConfigService: SearchConfigurationService,
|
private searchConfigService: SearchConfigurationService,
|
||||||
private rdbService: RemoteDataBuildService,
|
private rdbService: RemoteDataBuildService,
|
||||||
|
private submissionService: SubmissionService,
|
||||||
|
private submissionObjectService: SubmissionObjectDataService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
private router: Router,
|
private router: Router
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.setItem();
|
||||||
this.selection$ = this.selectableListService
|
this.selection$ = this.selectableListService
|
||||||
.getSelectableList(this.listId)
|
.getSelectableList(this.listId)
|
||||||
.pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
|
.pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
|
||||||
@@ -188,6 +206,24 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize this.item$ based on this.model.submissionId
|
||||||
|
*/
|
||||||
|
private setItem() {
|
||||||
|
const submissionObject$ = this.submissionObjectService
|
||||||
|
.findById(this.submissionId, true, true, followLink('item'), followLink('collection')).pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
);
|
||||||
|
|
||||||
|
const item$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
|
||||||
|
const collection$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.collection as Observable<RemoteData<Collection>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
|
||||||
|
|
||||||
|
this.subs.push(item$.subscribe((item) => this.item = item));
|
||||||
|
this.subs.push(collection$.subscribe((collection) => this.collection = collection));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a subscription updating relationships with name variants
|
* Add a subscription updating relationships with name variants
|
||||||
* @param sri The search result to track name variants for
|
* @param sri The search result to track name variants for
|
||||||
@@ -243,5 +279,8 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.router.navigate([], {});
|
this.router.navigate([], {});
|
||||||
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
|
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
|
||||||
|
this.subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -295,6 +295,7 @@ describe('FormBuilderService test suite', () => {
|
|||||||
notRepeatable: false,
|
notRepeatable: false,
|
||||||
relationshipConfig: undefined,
|
relationshipConfig: undefined,
|
||||||
submissionId: '1234',
|
submissionId: '1234',
|
||||||
|
isDraggable: true,
|
||||||
groupFactory: () => {
|
groupFactory: () => {
|
||||||
return [
|
return [
|
||||||
new DynamicInputModel({ id: 'testFormRowArrayGroupInput' })
|
new DynamicInputModel({ id: 'testFormRowArrayGroupInput' })
|
||||||
|
@@ -28,9 +28,10 @@ import { DynamicRelationGroupModel } from './ds-dynamic-form-ui/models/relation-
|
|||||||
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
|
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
|
||||||
import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
import { FormFieldMetadataValueObject } from './models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from './models/form-field-metadata-value.model';
|
||||||
import { isNgbDateStruct } from '../../date.util';
|
import { dateToString, isNgbDateStruct } from '../../date.util';
|
||||||
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-ui/ds-dynamic-form-constants';
|
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-ui/ds-dynamic-form-constants';
|
||||||
import { CONCAT_GROUP_SUFFIX, DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
|
import { CONCAT_GROUP_SUFFIX, DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
|
||||||
|
import { VIRTUAL_METADATA_PREFIX } from '../../../core/shared/metadata.models';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormBuilderService extends DynamicFormService {
|
export class FormBuilderService extends DynamicFormService {
|
||||||
@@ -121,8 +122,15 @@ export class FormBuilderService extends DynamicFormService {
|
|||||||
|
|
||||||
const normalizeValue = (controlModel, controlValue, controlModelIndex) => {
|
const normalizeValue = (controlModel, controlValue, controlModelIndex) => {
|
||||||
const controlLanguage = (controlModel as DsDynamicInputModel).hasLanguages ? (controlModel as DsDynamicInputModel).language : null;
|
const controlLanguage = (controlModel as DsDynamicInputModel).hasLanguages ? (controlModel as DsDynamicInputModel).language : null;
|
||||||
|
|
||||||
|
if (controlModel?.metadataValue?.authority?.includes(VIRTUAL_METADATA_PREFIX)) {
|
||||||
|
return controlModel.metadataValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isString(controlValue)) {
|
if (isString(controlValue)) {
|
||||||
return new FormFieldMetadataValueObject(controlValue, controlLanguage, null, null, controlModelIndex);
|
return new FormFieldMetadataValueObject(controlValue, controlLanguage, null, null, controlModelIndex);
|
||||||
|
} else if (isNgbDateStruct(controlValue)) {
|
||||||
|
return new FormFieldMetadataValueObject(dateToString(controlValue));
|
||||||
} else if (isObject(controlValue)) {
|
} else if (isObject(controlValue)) {
|
||||||
const authority = (controlValue as any).authority || (controlValue as any).id || null;
|
const authority = (controlValue as any).authority || (controlValue as any).id || null;
|
||||||
const place = controlModelIndex || (controlValue as any).place;
|
const place = controlModelIndex || (controlValue as any).place;
|
||||||
@@ -240,7 +248,7 @@ export class FormBuilderService extends DynamicFormService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasArrayGroupValue(model: DynamicFormControlModel): boolean {
|
hasArrayGroupValue(model: DynamicFormControlModel): boolean {
|
||||||
return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG || model.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY);
|
return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMappedGroupValue(model: DynamicFormControlModel): boolean {
|
hasMappedGroupValue(model: DynamicFormControlModel): boolean {
|
||||||
@@ -310,7 +318,7 @@ export class FormBuilderService extends DynamicFormService {
|
|||||||
let tempModel: DynamicFormControlModel;
|
let tempModel: DynamicFormControlModel;
|
||||||
|
|
||||||
if (this.isArrayGroup(model as DynamicFormControlModel)) {
|
if (this.isArrayGroup(model as DynamicFormControlModel)) {
|
||||||
return hasValue((model as any).metadataKey) ? (model as any).metadataKey : model.index.toString();
|
return model.index.toString();
|
||||||
} else if (this.isModelInCustomGroup(model as DynamicFormControlModel)) {
|
} else if (this.isModelInCustomGroup(model as DynamicFormControlModel)) {
|
||||||
tempModel = (model as any).parent;
|
tempModel = (model as any).parent;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -58,8 +58,20 @@ export class ConcatFieldParser extends FieldParser {
|
|||||||
concatGroup.group = [];
|
concatGroup.group = [];
|
||||||
concatGroup.separator = this.separator;
|
concatGroup.separator = this.separator;
|
||||||
|
|
||||||
const input1ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_FIRST_INPUT_SUFFIX, false, false);
|
const input1ModelConfig: DynamicInputModelConfig = this.initModel(
|
||||||
const input2ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_SECOND_INPUT_SUFFIX, false, false);
|
id + CONCAT_FIRST_INPUT_SUFFIX,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
const input2ModelConfig: DynamicInputModelConfig = this.initModel(
|
||||||
|
id + CONCAT_SECOND_INPUT_SUFFIX,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (hasNoValue(concatGroup.hint) && hasValue(input1ModelConfig.hint) && hasNoValue(input2ModelConfig.hint)) {
|
if (hasNoValue(concatGroup.hint) && hasValue(input1ModelConfig.hint) && hasNoValue(input2ModelConfig.hint)) {
|
||||||
concatGroup.hint = input1ModelConfig.hint;
|
concatGroup.hint = input1ModelConfig.hint;
|
||||||
|
@@ -15,6 +15,8 @@ import { setLayout } from './parser.utils';
|
|||||||
import { ParserOptions } from './parser-options';
|
import { ParserOptions } from './parser-options';
|
||||||
import { RelationshipOptions } from '../models/relationship-options.model';
|
import { RelationshipOptions } from '../models/relationship-options.model';
|
||||||
import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
|
import { ParserType } from './parser-type';
|
||||||
|
import { isNgbDateStruct } from '../../../date.util';
|
||||||
|
|
||||||
export const SUBMISSION_ID: InjectionToken<string> = new InjectionToken<string>('submissionId');
|
export const SUBMISSION_ID: InjectionToken<string> = new InjectionToken<string>('submissionId');
|
||||||
export const CONFIG_DATA: InjectionToken<FormFieldModel> = new InjectionToken<FormFieldModel>('configData');
|
export const CONFIG_DATA: InjectionToken<FormFieldModel> = new InjectionToken<FormFieldModel>('configData');
|
||||||
@@ -37,9 +39,8 @@ export abstract class FieldParser {
|
|||||||
|
|
||||||
public parse() {
|
public parse() {
|
||||||
if (((this.getInitValueCount() > 1 && !this.configData.repeatable) || (this.configData.repeatable))
|
if (((this.getInitValueCount() > 1 && !this.configData.repeatable) || (this.configData.repeatable))
|
||||||
&& (this.configData.input.type !== 'list')
|
&& (this.configData.input.type !== ParserType.List)
|
||||||
&& (this.configData.input.type !== 'tag')
|
&& (this.configData.input.type !== ParserType.Tag)
|
||||||
&& (this.configData.input.type !== 'group')
|
|
||||||
) {
|
) {
|
||||||
let arrayCounter = 0;
|
let arrayCounter = 0;
|
||||||
let fieldArrayCounter = 0;
|
let fieldArrayCounter = 0;
|
||||||
@@ -49,6 +50,11 @@ export abstract class FieldParser {
|
|||||||
if (Array.isArray(this.configData.selectableMetadata) && this.configData.selectableMetadata.length === 1) {
|
if (Array.isArray(this.configData.selectableMetadata) && this.configData.selectableMetadata.length === 1) {
|
||||||
metadataKey = this.configData.selectableMetadata[0].metadata;
|
metadataKey = this.configData.selectableMetadata[0].metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isDraggable = true;
|
||||||
|
if (this.configData.input.type === ParserType.Onebox && this.configData?.selectableMetadata?.length > 1) {
|
||||||
|
isDraggable = false;
|
||||||
|
}
|
||||||
const config = {
|
const config = {
|
||||||
id: uniqueId() + '_array',
|
id: uniqueId() + '_array',
|
||||||
label: this.configData.label,
|
label: this.configData.label,
|
||||||
@@ -60,6 +66,7 @@ export abstract class FieldParser {
|
|||||||
metadataKey,
|
metadataKey,
|
||||||
metadataFields: this.getAllFieldIds(),
|
metadataFields: this.getAllFieldIds(),
|
||||||
hasSelectableMetadata: isNotEmpty(this.configData.selectableMetadata),
|
hasSelectableMetadata: isNotEmpty(this.configData.selectableMetadata),
|
||||||
|
isDraggable,
|
||||||
groupFactory: () => {
|
groupFactory: () => {
|
||||||
let model;
|
let model;
|
||||||
if ((arrayCounter === 0)) {
|
if ((arrayCounter === 0)) {
|
||||||
@@ -69,19 +76,13 @@ export abstract class FieldParser {
|
|||||||
const fieldArrayOfValueLength = this.getInitValueCount(arrayCounter - 1);
|
const fieldArrayOfValueLength = this.getInitValueCount(arrayCounter - 1);
|
||||||
let fieldValue = null;
|
let fieldValue = null;
|
||||||
if (fieldArrayOfValueLength > 0) {
|
if (fieldArrayOfValueLength > 0) {
|
||||||
if (fieldArrayCounter === 0) {
|
fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter++);
|
||||||
fieldValue = '';
|
if (fieldArrayCounter === fieldArrayOfValueLength) {
|
||||||
} else {
|
|
||||||
fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter - 1);
|
|
||||||
}
|
|
||||||
fieldArrayCounter++;
|
|
||||||
if (fieldArrayCounter === fieldArrayOfValueLength + 1) {
|
|
||||||
fieldArrayCounter = 0;
|
fieldArrayCounter = 0;
|
||||||
arrayCounter++;
|
arrayCounter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model = this.modelFactory(fieldValue, false);
|
model = this.modelFactory(fieldValue, false);
|
||||||
model.id = `${model.id}_${fieldArrayCounter}`;
|
|
||||||
}
|
}
|
||||||
setLayout(model, 'element', 'host', 'col');
|
setLayout(model, 'element', 'host', 'col');
|
||||||
if (model.hasLanguages || isNotEmpty(model.relationship)) {
|
if (model.hasLanguages || isNotEmpty(model.relationship)) {
|
||||||
@@ -130,7 +131,9 @@ export abstract class FieldParser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof fieldValue === 'object') {
|
if (isNgbDateStruct(fieldValue)) {
|
||||||
|
modelConfig.value = fieldValue;
|
||||||
|
} else if (typeof fieldValue === 'object') {
|
||||||
modelConfig.metadataValue = fieldValue;
|
modelConfig.metadataValue = fieldValue;
|
||||||
modelConfig.language = fieldValue.language;
|
modelConfig.language = fieldValue.language;
|
||||||
modelConfig.place = fieldValue.place;
|
modelConfig.place = fieldValue.place;
|
||||||
@@ -210,10 +213,9 @@ export abstract class FieldParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getInitArrayIndex() {
|
protected getInitArrayIndex() {
|
||||||
let fieldCount = 0;
|
|
||||||
const fieldIds: any = this.getAllFieldIds();
|
const fieldIds: any = this.getAllFieldIds();
|
||||||
if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length === 1 && this.initFormValues.hasOwnProperty(fieldIds)) {
|
if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length === 1 && this.initFormValues.hasOwnProperty(fieldIds)) {
|
||||||
fieldCount = this.initFormValues[fieldIds].filter((value) => hasValue(value) && hasValue(value.value)).length;
|
return this.initFormValues[fieldIds].length;
|
||||||
} else if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length > 1) {
|
} else if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length > 1) {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
fieldIds.forEach((id) => {
|
fieldIds.forEach((id) => {
|
||||||
@@ -221,9 +223,10 @@ export abstract class FieldParser {
|
|||||||
counter = counter + this.initFormValues[id].length;
|
counter = counter + this.initFormValues[id].length;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fieldCount = counter;
|
return (counter === 0) ? 1 : counter;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
return (fieldCount === 0) ? 1 : fieldCount + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getFieldId(): string {
|
protected getFieldId(): string {
|
||||||
@@ -245,7 +248,7 @@ export abstract class FieldParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initModel(id?: string, label = true, setErrors = true, hint = true) {
|
protected initModel(id?: string, label = true, labelEmpty = false, setErrors = true, hint = true) {
|
||||||
|
|
||||||
const controlModel = Object.create(null);
|
const controlModel = Object.create(null);
|
||||||
|
|
||||||
@@ -316,7 +319,7 @@ export abstract class FieldParser {
|
|||||||
|
|
||||||
protected setLabel(controlModel, label = true, labelEmpty = false) {
|
protected setLabel(controlModel, label = true, labelEmpty = false) {
|
||||||
if (label) {
|
if (label) {
|
||||||
controlModel.label = this.configData.label;
|
controlModel.label = (labelEmpty) ? ' ' : this.configData.label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -59,7 +59,7 @@ export class OneboxFieldParser extends FieldParser {
|
|||||||
this.setLabel(inputSelectGroup, label);
|
this.setLabel(inputSelectGroup, label);
|
||||||
inputSelectGroup.required = isNotEmpty(this.configData.mandatory);
|
inputSelectGroup.required = isNotEmpty(this.configData.mandatory);
|
||||||
|
|
||||||
const selectModelConfig: DynamicSelectModelConfig<any> = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label, false);
|
const selectModelConfig: DynamicSelectModelConfig<any> = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label, false, false);
|
||||||
selectModelConfig.hint = null;
|
selectModelConfig.hint = null;
|
||||||
this.setOptions(selectModelConfig);
|
this.setOptions(selectModelConfig);
|
||||||
if (isNotEmpty(fieldValue)) {
|
if (isNotEmpty(fieldValue)) {
|
||||||
@@ -67,7 +67,7 @@ export class OneboxFieldParser extends FieldParser {
|
|||||||
}
|
}
|
||||||
inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect));
|
inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect));
|
||||||
|
|
||||||
const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false);
|
const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false, false);
|
||||||
inputModelConfig.hint = null;
|
inputModelConfig.hint = null;
|
||||||
this.setValues(inputModelConfig, fieldValue);
|
this.setValues(inputModelConfig, fieldValue);
|
||||||
inputSelectGroup.readOnly = selectModelConfig.disabled && inputModelConfig.readOnly;
|
inputSelectGroup.readOnly = selectModelConfig.disabled && inputModelConfig.readOnly;
|
||||||
|
@@ -1,57 +1,68 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<form class="form-horizontal" [formGroup]="formGroup">
|
<form class="form-horizontal" [formGroup]="formGroup">
|
||||||
|
|
||||||
<ds-dynamic-form
|
<ds-dynamic-form
|
||||||
[formId]="formId"
|
[formId]="formId"
|
||||||
[formGroup]="formGroup"
|
[formGroup]="formGroup"
|
||||||
[formModel]="formModel"
|
[formModel]="formModel"
|
||||||
[formLayout]="formLayout"
|
[formLayout]="formLayout"
|
||||||
(change)="$event.stopPropagation();"
|
(change)="$event.stopPropagation();"
|
||||||
(dfBlur)="onBlur($event)"
|
(dfBlur)="onBlur($event)"
|
||||||
(dfChange)="onChange($event)"
|
(dfChange)="onChange($event)"
|
||||||
(dfFocus)="onFocus($event)">
|
(dfFocus)="onFocus($event)"
|
||||||
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
|
(ngbEvent)="onCustomEvent($event)">
|
||||||
<!--Array with repeatable items-->
|
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
|
||||||
<div *ngIf="context.hasSelectableMetadata && !context.notRepeatable && index < 1"
|
<!--Array with repeatable items-->
|
||||||
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
<div *ngIf="(!context.notRepeatable) && !isVirtual(context, index)"
|
||||||
<div class="btn-group" role="group" aria-label="Add and remove button">
|
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
||||||
<button type="button" class="btn btn-secondary"
|
<button type="button" class="btn btn-secondary"
|
||||||
[disabled]="isItemReadOnly(context, index)"
|
title="{{'form.remove' | translate}}"
|
||||||
(click)="insertItem($event, group.context, group.index)">
|
(click)="removeItem($event, context, index)"
|
||||||
<span aria-label="Add">{{'form.add' | translate}}</span>
|
[disabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)">
|
||||||
</button>
|
<span attr.aria-label="{{'form.remove' | translate}}"><i class="fas fa-trash" aria-hidden="true"></i></span>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="(!context.notRepeatable) && index === (group.context.groups.length - 1)" class="clearfix pl-4 w-100">
|
||||||
<!--Array with non repeatable items - Only delete button-->
|
<div class="btn-group" role="group" aria-label="remove button">
|
||||||
<div *ngIf="context.notRepeatable && group.context.groups.length > 1 || index > 0 && !(group.group[0]?.value?.isVirtual || group.group[0]?.metadataValue?.isVirtual)"
|
<button type="button" class="ds-form-add-more btn btn-link"
|
||||||
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
title="{{'form.add' | translate}}"
|
||||||
<div class="btn-group" role="group" aria-label="Remove button">
|
[disabled]="isItemReadOnly(context, index)"
|
||||||
<button type="button" class="btn btn-secondary"
|
(click)="insertItem($event, group.context, group.context.groups.length)">
|
||||||
(click)="removeItem($event, context, index)"
|
<span attr.aria-label="{{'form.add' | translate}}"><i class="fas fa-plus"></i> {{'form.add' | translate}}</span>
|
||||||
[disabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)">
|
</button>
|
||||||
<i class="fas fa-trash" aria-hidden="true"></i>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
</ds-dynamic-form>
|
|
||||||
|
|
||||||
<ng-content></ng-content>
|
|
||||||
|
|
||||||
<div *ngIf="displaySubmit">
|
|
||||||
<hr>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col text-right">
|
|
||||||
<button type="reset" class="btn btn-default" (click)="reset()">{{cancelLabel | translate}}</button>
|
|
||||||
<button type="submit" class="btn btn-primary" (click)="onSubmit()"
|
|
||||||
[disabled]="!(isValid() | async)">{{submitLabel | translate}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
<!--Array with non repeatable items - Only discard button-->
|
||||||
|
<div *ngIf="context.notRepeatable && context.showButtons && group.context.groups.length > 1"
|
||||||
|
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
||||||
|
<div class="btn-group" role="group" aria-label="Remove button">
|
||||||
|
<button type="button" class="btn btn-secondary"
|
||||||
|
title="{{'form.discard' | translate}}"
|
||||||
|
(click)="removeItem($event, context, index)"
|
||||||
|
[disabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)">
|
||||||
|
<span attr.aria-label="{{'form.discard' | translate}}">{{'form.discard' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ds-dynamic-form>
|
||||||
|
|
||||||
|
<ng-content></ng-content>
|
||||||
|
|
||||||
|
<div *ngIf="displaySubmit">
|
||||||
|
<hr>
|
||||||
|
<div class="form-group row">
|
||||||
|
<div class="col text-right">
|
||||||
|
<button type="reset" class="btn btn-default" (click)="reset()">{{cancelLabel | translate}}</button>
|
||||||
|
<button type="submit" class="btn btn-primary" (click)="onSubmit()"
|
||||||
|
[disabled]="!(isValid() | async)">{{submitLabel | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -15,6 +15,11 @@
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.ds-form-add-more:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.ds-form-input-value {
|
.ds-form-input-value {
|
||||||
border-top-left-radius: 0 !important;
|
border-top-left-radius: 0 !important;
|
||||||
border-bottom-left-radius: 0 !important;
|
border-bottom-left-radius: 0 !important;
|
||||||
|
@@ -418,7 +418,7 @@ describe('FormComponent test suite', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should dispatch FormChangeAction when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
|
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, 1);
|
formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
|
||||||
|
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
|
expect(store.dispatch).toHaveBeenCalledWith(new FormChangeAction('testFormArray', service.getValueFromModel(formComp.formModel)));
|
||||||
}));
|
}));
|
||||||
@@ -426,7 +426,7 @@ describe('FormComponent test suite', () => {
|
|||||||
it('should emit removeArrayItem Event when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
|
it('should emit removeArrayItem Event when an item has been removed from an array', inject([FormBuilderService], (service: FormBuilderService) => {
|
||||||
spyOn(formComp.removeArrayItem, 'emit');
|
spyOn(formComp.removeArrayItem, 'emit');
|
||||||
|
|
||||||
formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 1);
|
formComp.removeItem(new Event('click'), formComp.formModel[0] as DynamicFormArrayModel, 0);
|
||||||
|
|
||||||
expect(formComp.removeArrayItem.emit).toHaveBeenCalled();
|
expect(formComp.removeArrayItem.emit).toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
|
@@ -2,6 +2,7 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators';
|
|||||||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
import { ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
|
import { AbstractControl, FormArray, FormControl, FormGroup } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
DynamicFormArrayModel,
|
DynamicFormArrayModel,
|
||||||
DynamicFormControlEvent,
|
DynamicFormControlEvent,
|
||||||
@@ -9,15 +10,14 @@ import {
|
|||||||
DynamicFormGroupModel,
|
DynamicFormGroupModel,
|
||||||
DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { findIndex } from 'lodash';
|
import { findIndex } from 'lodash';
|
||||||
|
|
||||||
import { FormBuilderService } from './builder/form-builder.service';
|
import { FormBuilderService } from './builder/form-builder.service';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
|
||||||
import { hasValue, isNotEmpty, isNotNull, isNull } from '../empty.util';
|
import { hasValue, isNotEmpty, isNotNull, isNull } from '../empty.util';
|
||||||
import { FormService } from './form.service';
|
import { FormService } from './form.service';
|
||||||
import { FormEntry, FormError } from './form.reducer';
|
import { FormEntry, FormError } from './form.reducer';
|
||||||
import { QUALDROP_GROUP_SUFFIX } from './builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
|
import { FormFieldMetadataValueObject } from './builder/models/form-field-metadata-value.model';
|
||||||
|
|
||||||
const QUALDROP_GROUP_REGEX = new RegExp(`${QUALDROP_GROUP_SUFFIX}_\\d+$`);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default form component.
|
* The default form component.
|
||||||
@@ -70,6 +70,7 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
@Output('dfBlur') blur: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
@Output('dfBlur') blur: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
@Output('dfChange') change: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
@Output('dfChange') change: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
@Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
@Output('dfFocus') focus: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
|
@Output('ngbEvent') customEvent: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
/* tslint:enable:no-output-rename */
|
/* tslint:enable:no-output-rename */
|
||||||
@Output() addArrayItem: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
@Output() addArrayItem: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
@Output() removeArrayItem: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
@Output() removeArrayItem: EventEmitter<DynamicFormControlEvent> = new EventEmitter<DynamicFormControlEvent>();
|
||||||
@@ -87,9 +88,9 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
@Output() submitForm: EventEmitter<Observable<any>> = new EventEmitter<Observable<any>>();
|
@Output() submitForm: EventEmitter<Observable<any>> = new EventEmitter<Observable<any>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object of FormGroup type
|
* Reference to NgbModal
|
||||||
*/
|
*/
|
||||||
// public formGroup: FormGroup;
|
modalRef: NgbModalRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
@@ -166,7 +167,6 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))),
|
filter((formState: FormEntry) => !!formState && (isNotEmpty(formState.errors) || isNotEmpty(this.formErrors))),
|
||||||
map((formState) => formState.errors),
|
map((formState) => formState.errors),
|
||||||
distinctUntilChanged())
|
distinctUntilChanged())
|
||||||
// .delay(100) // this terrible delay is here to prevent the detection change error
|
|
||||||
.subscribe((errors: FormError[]) => {
|
.subscribe((errors: FormError[]) => {
|
||||||
const { formGroup, formModel } = this;
|
const { formGroup, formModel } = this;
|
||||||
errors
|
errors
|
||||||
@@ -187,7 +187,6 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
if (field) {
|
if (field) {
|
||||||
const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel);
|
const model: DynamicFormControlModel = this.formBuilderService.findById(fieldId, formModel);
|
||||||
this.formService.addErrorToField(field, model, error.message);
|
this.formService.addErrorToField(field, model, error.message);
|
||||||
// this.formService.validateAllFormFields(formGroup);
|
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -252,6 +251,10 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
this.blur.emit(event);
|
this.blur.emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCustomEvent(event: any) {
|
||||||
|
this.customEvent.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
onFocus(event: DynamicFormControlEvent): void {
|
onFocus(event: DynamicFormControlEvent): void {
|
||||||
this.formService.setTouched(this.formId, this.formModel, event);
|
this.formService.setTouched(this.formId, this.formModel, event);
|
||||||
this.focus.emit(event);
|
this.focus.emit(event);
|
||||||
@@ -300,58 +303,25 @@ export class FormComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
|
removeItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
|
||||||
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
|
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
|
||||||
this.removeArrayItem.emit(this.getEvent($event, arrayContext, index - 1, 'remove'));
|
const event = this.getEvent($event, arrayContext, index, 'remove');
|
||||||
this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext);
|
this.formBuilderService.removeFormArrayGroup(index, formArrayControl, arrayContext);
|
||||||
this.formService.changeForm(this.formId, this.formModel);
|
this.formService.changeForm(this.formId, this.formModel);
|
||||||
|
this.removeArrayItem.emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
|
insertItem($event, arrayContext: DynamicFormArrayModel, index: number): void {
|
||||||
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
|
const formArrayControl = this.formGroup.get(this.formBuilderService.getPath(arrayContext)) as FormArray;
|
||||||
|
this.formBuilderService.insertFormArrayGroup(index, formArrayControl, arrayContext);
|
||||||
// First emit the new value so it can be sent to the server
|
this.addArrayItem.emit(this.getEvent($event, arrayContext, index, 'add'));
|
||||||
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.hasAuthority) {
|
|
||||||
model.value = Object.values(value)[0];
|
|
||||||
const ctrl = formArrayControl.controls[formArrayControl.length - 1];
|
|
||||||
const ctrlValue = ctrl.value;
|
|
||||||
const ctrlValueKey = Object.keys(ctrlValue)[0];
|
|
||||||
ctrl.setValue({
|
|
||||||
[ctrlValueKey]: model.value
|
|
||||||
});
|
|
||||||
} 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);
|
this.formService.changeForm(this.formId, this.formModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isVirtual(arrayContext: DynamicFormArrayModel, index: number) {
|
||||||
|
const context = arrayContext.groups[index];
|
||||||
|
const value: FormFieldMetadataValueObject = (context.group[0] as any).metadataValue;
|
||||||
|
return isNotEmpty(value) && value.isVirtual;
|
||||||
|
}
|
||||||
|
|
||||||
protected getEvent($event: any, arrayContext: DynamicFormArrayModel, index: number, type: string): DynamicFormControlEvent {
|
protected getEvent($event: any, arrayContext: DynamicFormArrayModel, index: number, type: string): DynamicFormControlEvent {
|
||||||
const context = arrayContext.groups[index];
|
const context = arrayContext.groups[index];
|
||||||
const itemGroupModel = context.context;
|
const itemGroupModel = context.context;
|
||||||
|
@@ -80,6 +80,7 @@ const rowArrayQualdropConfig = {
|
|||||||
id: 'row_QUALDROP_GROUP',
|
id: 'row_QUALDROP_GROUP',
|
||||||
initialCount: 1,
|
initialCount: 1,
|
||||||
notRepeatable: true,
|
notRepeatable: true,
|
||||||
|
isDraggable: false,
|
||||||
relationshipConfig: undefined,
|
relationshipConfig: undefined,
|
||||||
groupFactory: () => {
|
groupFactory: () => {
|
||||||
return [MockQualdropModel];
|
return [MockQualdropModel];
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { waitForAsync, TestBed } from '@angular/core/testing';
|
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
@@ -35,10 +35,10 @@ describe('SectionFormOperationsService test suite', () => {
|
|||||||
let serviceAsAny: any;
|
let serviceAsAny: any;
|
||||||
|
|
||||||
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
|
||||||
add: jasmine.createSpy('add'),
|
add: jasmine.createSpy('add'),
|
||||||
replace: jasmine.createSpy('replace'),
|
replace: jasmine.createSpy('replace'),
|
||||||
remove: jasmine.createSpy('remove'),
|
remove: jasmine.createSpy('remove'),
|
||||||
});
|
});
|
||||||
const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'test');
|
const pathCombiner = new JsonPatchOperationPathCombiner('sections', 'test');
|
||||||
|
|
||||||
const dynamicFormControlChangeEvent: DynamicFormControlEvent = {
|
const dynamicFormControlChangeEvent: DynamicFormControlEvent = {
|
||||||
|
@@ -6,18 +6,10 @@ import {
|
|||||||
DYNAMIC_FORM_CONTROL_TYPE_GROUP,
|
DYNAMIC_FORM_CONTROL_TYPE_GROUP,
|
||||||
DynamicFormArrayGroupModel,
|
DynamicFormArrayGroupModel,
|
||||||
DynamicFormControlEvent,
|
DynamicFormControlEvent,
|
||||||
DynamicFormControlModel
|
DynamicFormControlModel, isDynamicFormControlEvent
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
import {
|
import { hasValue, isNotEmpty, isNotNull, isNotUndefined, isNull, isUndefined } from '../../../shared/empty.util';
|
||||||
hasNoValue,
|
|
||||||
hasValue,
|
|
||||||
isNotEmpty,
|
|
||||||
isNotNull,
|
|
||||||
isNotUndefined,
|
|
||||||
isNull,
|
|
||||||
isUndefined
|
|
||||||
} from '../../../shared/empty.util';
|
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object';
|
import { FormFieldPreviousValueObject } from '../../../shared/form/builder/models/form-field-previous-value-object';
|
||||||
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
@@ -30,6 +22,8 @@ import { DynamicQualdropModel } from '../../../shared/form/builder/ds-dynamic-fo
|
|||||||
import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
import { DynamicRelationGroupModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
||||||
import { VocabularyEntryDetail } from '../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { deepClone } from 'fast-json-patch';
|
import { deepClone } from 'fast-json-patch';
|
||||||
|
import { dateToString, isNgbDateStruct } from '../../../shared/date.util';
|
||||||
|
import { DynamicRowArrayModel } from '../../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all form section operations
|
* The service handling all form section operations
|
||||||
@@ -71,8 +65,8 @@ export class SectionFormOperationsService {
|
|||||||
case 'change':
|
case 'change':
|
||||||
this.dispatchOperationsFromChangeEvent(pathCombiner, event, previousValue, hasStoredValue);
|
this.dispatchOperationsFromChangeEvent(pathCombiner, event, previousValue, hasStoredValue);
|
||||||
break;
|
break;
|
||||||
case 'add':
|
case 'move':
|
||||||
this.dispatchOperationsFromAddEvent(pathCombiner, event);
|
this.dispatchOperationsFromMoveEvent(pathCombiner, event, previousValue);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -83,20 +77,29 @@ export class SectionFormOperationsService {
|
|||||||
* Return index if specified field is part of fields array
|
* Return index if specified field is part of fields array
|
||||||
*
|
*
|
||||||
* @param event
|
* @param event
|
||||||
* the [[DynamicFormControlEvent]] for the specified operation
|
* the [[DynamicFormControlEvent]] | CustomEvent for the specified operation
|
||||||
* @return number
|
* @return number
|
||||||
* the array index is part of array, zero otherwise
|
* the array index is part of array, zero otherwise
|
||||||
*/
|
*/
|
||||||
public getArrayIndexFromEvent(event: DynamicFormControlEvent): number {
|
public getArrayIndexFromEvent(event: DynamicFormControlEvent | any): number {
|
||||||
let fieldIndex: number;
|
let fieldIndex: number;
|
||||||
|
|
||||||
if (isNotEmpty(event)) {
|
if (isNotEmpty(event)) {
|
||||||
if (isNull(event.context)) {
|
if (isDynamicFormControlEvent(event)) {
|
||||||
// Check whether model is part of an Array of group
|
// This is the case of a default insertItem/removeItem event
|
||||||
if (this.isPartOfArrayOfGroup(event.model)) {
|
|
||||||
fieldIndex = (event.model.parent as any).parent.index;
|
if (isNull(event.context)) {
|
||||||
|
// Check whether model is part of an Array of group
|
||||||
|
if (this.isPartOfArrayOfGroup(event.model)) {
|
||||||
|
fieldIndex = (event.model.parent as any).parent.index;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldIndex = event.context.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
fieldIndex = event.context.index;
|
// This is the case of a custom event which contains indexes information
|
||||||
|
fieldIndex = event.index as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +248,8 @@ export class SectionFormOperationsService {
|
|||||||
// Language without Authority (input, textArea)
|
// Language without Authority (input, textArea)
|
||||||
fieldValue = new FormFieldMetadataValueObject(value, language);
|
fieldValue = new FormFieldMetadataValueObject(value, language);
|
||||||
}
|
}
|
||||||
|
} else if (isNgbDateStruct(value)) {
|
||||||
|
fieldValue = new FormFieldMetadataValueObject(dateToString(value));
|
||||||
} else if (value instanceof FormFieldLanguageValueObject || value instanceof VocabularyEntry
|
} else if (value instanceof FormFieldLanguageValueObject || value instanceof VocabularyEntry
|
||||||
|| value instanceof VocabularyEntryDetail || isObject(value)) {
|
|| value instanceof VocabularyEntryDetail || isObject(value)) {
|
||||||
fieldValue = value;
|
fieldValue = value;
|
||||||
@@ -291,11 +296,19 @@ export class SectionFormOperationsService {
|
|||||||
protected dispatchOperationsFromRemoveEvent(pathCombiner: JsonPatchOperationPathCombiner,
|
protected dispatchOperationsFromRemoveEvent(pathCombiner: JsonPatchOperationPathCombiner,
|
||||||
event: DynamicFormControlEvent,
|
event: DynamicFormControlEvent,
|
||||||
previousValue: FormFieldPreviousValueObject): void {
|
previousValue: FormFieldPreviousValueObject): void {
|
||||||
|
|
||||||
|
if (event.context && event.context instanceof DynamicFormArrayGroupModel) {
|
||||||
|
// Model is a DynamicRowArrayModel
|
||||||
|
this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const path = this.getFieldPathFromEvent(event);
|
const path = this.getFieldPathFromEvent(event);
|
||||||
const value = this.getFieldValueFromChangeEvent(event);
|
const value = this.getFieldValueFromChangeEvent(event);
|
||||||
|
console.log(value);
|
||||||
if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) {
|
if (this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) {
|
||||||
this.dispatchOperationsFromMap(this.getQualdropValueMap(event), pathCombiner, event, previousValue);
|
this.dispatchOperationsFromMap(this.getQualdropValueMap(event), pathCombiner, event, previousValue);
|
||||||
} else if (isNotEmpty(value)) {
|
} else if ((isNotEmpty(value) && typeof value === 'string') || (isNotEmpty(value) && value instanceof FormFieldMetadataValueObject && value.hasValue())) {
|
||||||
this.operationsBuilder.remove(pathCombiner.getPath(path));
|
this.operationsBuilder.remove(pathCombiner.getPath(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,17 +365,25 @@ export class SectionFormOperationsService {
|
|||||||
event: DynamicFormControlEvent,
|
event: DynamicFormControlEvent,
|
||||||
previousValue: FormFieldPreviousValueObject,
|
previousValue: FormFieldPreviousValueObject,
|
||||||
hasStoredValue: boolean): void {
|
hasStoredValue: boolean): void {
|
||||||
|
|
||||||
|
if (event.context && event.context instanceof DynamicFormArrayGroupModel) {
|
||||||
|
// Model is a DynamicRowArrayModel
|
||||||
|
this.handleArrayGroupPatch(pathCombiner, event, (event as any).context.context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const path = this.getFieldPathFromEvent(event);
|
const path = this.getFieldPathFromEvent(event);
|
||||||
const segmentedPath = this.getFieldPathSegmentedFromChangeEvent(event);
|
const segmentedPath = this.getFieldPathSegmentedFromChangeEvent(event);
|
||||||
const value = this.getFieldValueFromChangeEvent(event);
|
const value = this.getFieldValueFromChangeEvent(event);
|
||||||
// Detect which operation must be dispatched
|
// Detect which operation must be dispatched
|
||||||
if (this.formBuilder.isQualdropGroup(event.model.parent as DynamicFormControlModel)) {
|
if (this.formBuilder.isQualdropGroup(event.model.parent as DynamicFormControlModel)
|
||||||
|
|| this.formBuilder.isQualdropGroup(event.model as DynamicFormControlModel)) {
|
||||||
// It's a qualdrup model
|
// It's a qualdrup model
|
||||||
this.dispatchOperationsFromMap(this.getQualdropValueMap(event), pathCombiner, event, previousValue);
|
this.dispatchOperationsFromMap(this.getQualdropValueMap(event), pathCombiner, event, previousValue);
|
||||||
} else if (this.formBuilder.isRelationGroup(event.model)) {
|
} else if (this.formBuilder.isRelationGroup(event.model)) {
|
||||||
// It's a relation model
|
// It's a relation model
|
||||||
this.dispatchOperationsFromMap(this.getValueMap(value), pathCombiner, event, previousValue);
|
this.dispatchOperationsFromMap(this.getValueMap(value), pathCombiner, event, previousValue);
|
||||||
} else if (this.formBuilder.hasArrayGroupValue(event.model) && hasNoValue((event.model as any).relationshipConfig)) {
|
} else if (this.formBuilder.hasArrayGroupValue(event.model)) {
|
||||||
// Model has as value an array, so dispatch an add operation with entire block of values
|
// Model has as value an array, so dispatch an add operation with entire block of values
|
||||||
this.operationsBuilder.add(
|
this.operationsBuilder.add(
|
||||||
pathCombiner.getPath(segmentedPath),
|
pathCombiner.getPath(segmentedPath),
|
||||||
@@ -398,13 +419,21 @@ export class SectionFormOperationsService {
|
|||||||
value);
|
value);
|
||||||
}
|
}
|
||||||
previousValue.delete();
|
previousValue.delete();
|
||||||
} else if (value.hasValue() && (isUndefined(this.getArrayIndexFromEvent(event))
|
} else if (value.hasValue()) {
|
||||||
|| this.getArrayIndexFromEvent(event) === 0)) {
|
// Here model has no previous value but a new one
|
||||||
|
if (isUndefined(this.getArrayIndexFromEvent(event)) || this.getArrayIndexFromEvent(event) === 0) {
|
||||||
// Model is single field or is part of an array model but is the first item,
|
// Model is single field or is part of an array model but is the first item,
|
||||||
// so dispatch an add operation that initialize the values of a specific metadata
|
// so dispatch an add operation that initialize the values of a specific metadata
|
||||||
this.operationsBuilder.add(
|
this.operationsBuilder.add(
|
||||||
pathCombiner.getPath(segmentedPath),
|
pathCombiner.getPath(segmentedPath),
|
||||||
value, true);
|
value, true);
|
||||||
|
} else {
|
||||||
|
// Model is part of an array model but is not the first item,
|
||||||
|
// so dispatch an add operation that add a value to an existent metadata
|
||||||
|
this.operationsBuilder.add(
|
||||||
|
pathCombiner.getPath(path),
|
||||||
|
value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,4 +483,38 @@ export class SectionFormOperationsService {
|
|||||||
|
|
||||||
previousValue.delete();
|
previousValue.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle form move operations
|
||||||
|
*
|
||||||
|
* @param pathCombiner
|
||||||
|
* the [[JsonPatchOperationPathCombiner]] object for the specified operation
|
||||||
|
* @param event
|
||||||
|
* the [[DynamicFormControlEvent]] for the specified operation
|
||||||
|
* @param previousValue
|
||||||
|
* the [[FormFieldPreviousValueObject]] for the specified operation
|
||||||
|
*/
|
||||||
|
private dispatchOperationsFromMoveEvent(pathCombiner: JsonPatchOperationPathCombiner,
|
||||||
|
event: DynamicFormControlEvent,
|
||||||
|
previousValue: FormFieldPreviousValueObject) {
|
||||||
|
|
||||||
|
return this.handleArrayGroupPatch(pathCombiner, event.$event, (event as any).$event.arrayModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific patch handler for a DynamicRowArrayModel.
|
||||||
|
* Configure a Patch ADD with the current array value.
|
||||||
|
* @param pathCombiner
|
||||||
|
* @param event
|
||||||
|
* @param model
|
||||||
|
*/
|
||||||
|
private handleArrayGroupPatch(pathCombiner: JsonPatchOperationPathCombiner,
|
||||||
|
event,
|
||||||
|
model: DynamicRowArrayModel) {
|
||||||
|
const arrayValue = this.formBuilder.getValueFromModel([model]);
|
||||||
|
const segmentedPath2 = this.getFieldPathSegmentedFromChangeEvent(event);
|
||||||
|
this.operationsBuilder.add(
|
||||||
|
pathCombiner.getPath(segmentedPath2),
|
||||||
|
arrayValue[segmentedPath2], false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,4 +6,5 @@
|
|||||||
(dfChange)="onChange($event)"
|
(dfChange)="onChange($event)"
|
||||||
(dfFocus)="onFocus($event)"
|
(dfFocus)="onFocus($event)"
|
||||||
(remove)="onRemove($event)"
|
(remove)="onRemove($event)"
|
||||||
|
(ngbEvent)="onCustomEvent($event)"
|
||||||
(removeArrayItem)="onRemove($event)"></ds-form>
|
(removeArrayItem)="onRemove($event)"></ds-form>
|
||||||
|
@@ -19,7 +19,7 @@ import { FormComponent } from '../../../shared/form/form.component';
|
|||||||
import { FormService } from '../../../shared/form/form.service';
|
import { FormService } from '../../../shared/form/form.service';
|
||||||
import { SectionModelComponent } from '../models/section.model';
|
import { SectionModelComponent } from '../models/section.model';
|
||||||
import { SubmissionFormsConfigService } from '../../../core/config/submission-forms-config.service';
|
import { SubmissionFormsConfigService } from '../../../core/config/submission-forms-config.service';
|
||||||
import { hasNoValue, hasValue, isNotEmpty, isUndefined } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty, isUndefined } from '../../../shared/empty.util';
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
||||||
import { SubmissionSectionError, SubmissionSectionObject } from '../../objects/submission-objects.reducer';
|
import { SubmissionSectionError, SubmissionSectionObject } from '../../objects/submission-objects.reducer';
|
||||||
@@ -359,19 +359,16 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
|
|||||||
* the [[DynamicFormControlEvent]] emitted
|
* the [[DynamicFormControlEvent]] emitted
|
||||||
*/
|
*/
|
||||||
onChange(event: DynamicFormControlEvent): void {
|
onChange(event: DynamicFormControlEvent): void {
|
||||||
// don't handle change events for things with an index < 0, those are template rows.
|
this.formOperationsService.dispatchOperationsFromEvent(
|
||||||
if (hasNoValue(event.context) || hasNoValue(event.context.index) || event.context.index >= 0) {
|
this.pathCombiner,
|
||||||
this.formOperationsService.dispatchOperationsFromEvent(
|
event,
|
||||||
this.pathCombiner,
|
this.previousValue,
|
||||||
event,
|
this.hasStoredValue(this.formBuilderService.getId(event.model), this.formOperationsService.getArrayIndexFromEvent(event)));
|
||||||
this.previousValue,
|
const metadata = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event);
|
||||||
this.hasStoredValue(this.formBuilderService.getId(event.model), this.formOperationsService.getArrayIndexFromEvent(event)));
|
const value = this.formOperationsService.getFieldValueFromChangeEvent(event);
|
||||||
const metadata = this.formOperationsService.getFieldPathSegmentedFromChangeEvent(event);
|
|
||||||
const value = this.formOperationsService.getFieldValueFromChangeEvent(event);
|
|
||||||
|
|
||||||
if (environment.submission.autosave.metadata.indexOf(metadata) !== -1 && isNotEmpty(value)) {
|
if (environment.submission.autosave.metadata.indexOf(metadata) !== -1 && isNotEmpty(value)) {
|
||||||
this.submissionService.dispatchSave(this.submissionId);
|
this.submissionService.dispatchSave(this.submissionId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,4 +448,17 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
|
|||||||
isFieldToRemove(fieldId, index) {
|
isFieldToRemove(fieldId, index) {
|
||||||
return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index);
|
return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the customEvent (ex. drag-drop move event).
|
||||||
|
* The customEvent is stored inside event.$event
|
||||||
|
* @param $event
|
||||||
|
*/
|
||||||
|
onCustomEvent(event: DynamicFormControlEvent) {
|
||||||
|
this.formOperationsService.dispatchOperationsFromEvent(
|
||||||
|
this.pathCombiner,
|
||||||
|
event,
|
||||||
|
this.previousValue,
|
||||||
|
null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1267,7 +1267,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
"form.add": "Add",
|
"form.add": "Add more",
|
||||||
|
|
||||||
"form.add-help": "Click here to add the current entry and to add another one",
|
"form.add-help": "Click here to add the current entry and to add another one",
|
||||||
|
|
||||||
@@ -1277,6 +1277,8 @@
|
|||||||
|
|
||||||
"form.clear-help": "Click here to remove the selected value",
|
"form.clear-help": "Click here to remove the selected value",
|
||||||
|
|
||||||
|
"form.discard": "Discard",
|
||||||
|
|
||||||
"form.edit": "Edit",
|
"form.edit": "Edit",
|
||||||
|
|
||||||
"form.edit-help": "Click here to edit the selected value",
|
"form.edit-help": "Click here to edit the selected value",
|
||||||
@@ -1317,6 +1319,7 @@
|
|||||||
|
|
||||||
"form.submit": "Submit",
|
"form.submit": "Submit",
|
||||||
|
|
||||||
|
"form.repeatable.sort.tip": "Drop the item in the new position",
|
||||||
|
|
||||||
|
|
||||||
"home.description": "",
|
"home.description": "",
|
||||||
|
@@ -56,3 +56,10 @@ ds-admin-sidebar {
|
|||||||
left: auto;
|
left: auto;
|
||||||
width: calc(100% - 53px);
|
width: calc(100% - 53px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ds-submission-reorder-dragging {
|
||||||
|
.ds-hint,
|
||||||
|
button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user