Skip to content

Commit fd7aeac

Browse files
authored
Merge pull request #960 from 4Science/CSTPER-260
Auto-save in new Item Submission form breaks the form
2 parents e867bad + 6cf6dee commit fd7aeac

29 files changed

Lines changed: 1030 additions & 73 deletions

src/app/core/json-patch/json-patch-operations.service.spec.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { getTestScheduler } from 'jasmine-marbles';
1+
import { getTestScheduler, hot } from 'jasmine-marbles';
22
import { TestScheduler } from 'rxjs/testing';
33
import { of as observableOf } from 'rxjs';
44
import { catchError } from 'rxjs/operators';
55
import { Store } from '@ngrx/store';
6-
76
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
87
import { RequestService } from '../data/request.service';
98
import { SubmissionPatchRequest } from '../data/request.models';
@@ -22,6 +21,7 @@ import {
2221
} from './json-patch-operations.actions';
2322
import { RequestEntry } from '../data/request.reducer';
2423
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
24+
import { _deepClone } from 'fast-json-patch/lib/helpers';
2525

2626
class TestService extends JsonPatchOperationsService<SubmitDataResponseDefinitionObject, SubmissionPatchRequest> {
2727
protected linkPath = '';
@@ -196,6 +196,32 @@ describe('JsonPatchOperationsService test suite', () => {
196196
});
197197
});
198198

199+
describe('hasPendingOperations', () => {
200+
201+
it('should return true when there are pending operations', () => {
202+
203+
const expected = hot('(x|)', { x: true });
204+
205+
const result = service.hasPendingOperations(testJsonPatchResourceType);
206+
expect(result).toBeObservable(expected);
207+
208+
});
209+
210+
it('should return false when there are not pending operations', () => {
211+
212+
const mockStateNoOp = _deepClone(mockState);
213+
mockStateNoOp['json/patch'][testJsonPatchResourceType].children = [];
214+
store.select.and.returnValue(observableOf(mockStateNoOp['json/patch'][testJsonPatchResourceType]));
215+
216+
const expected = hot('(x|)', { x: false });
217+
218+
const result = service.hasPendingOperations(testJsonPatchResourceType);
219+
expect(result).toBeObservable(expected);
220+
221+
});
222+
223+
});
224+
199225
describe('jsonPatchByResourceID', () => {
200226

201227
it('should call submitJsonPatchOperations method', () => {

src/app/core/json-patch/json-patch-operations.service.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,18 @@ export abstract class JsonPatchOperationsService<ResponseDefinitionDomain, Patch
161161
return this.submitJsonPatchOperations(href$, resourceType);
162162
}
163163

164+
/**
165+
* Select the jsonPatch operation related to the specified resource type.
166+
* @param resourceType
167+
*/
168+
public hasPendingOperations(resourceType: string): Observable<boolean> {
169+
return this.store.select(jsonPatchOperationsByResourceType(resourceType)).pipe(
170+
map((val) => !isEmpty(val) && Object.values(val.children)
171+
.filter((section) => !isEmpty((section as any).body)).length > 0),
172+
distinctUntilChanged(),
173+
);
174+
}
175+
164176
/**
165177
* Make a new JSON Patch request with all operations related to the specified resource id
166178
*

src/app/shared/form/builder/form-builder.service.ts

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
DYNAMIC_FORM_CONTROL_TYPE_GROUP,
88
DYNAMIC_FORM_CONTROL_TYPE_INPUT,
99
DYNAMIC_FORM_CONTROL_TYPE_RADIO_GROUP,
10-
DynamicFormArrayModel,
10+
DynamicFormArrayModel, DynamicFormControlEvent,
1111
DynamicFormControlModel,
1212
DynamicFormGroupModel,
1313
DynamicFormService, DynamicFormValidationService,
@@ -16,7 +16,7 @@ import {
1616
import { isObject, isString, mergeWith } from 'lodash';
1717

1818
import { hasValue, isEmpty, isNotEmpty, isNotNull, isNotUndefined, isNull } from '../../empty.util';
19-
import { DynamicQualdropModel } from './ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
19+
import {DynamicQualdropModel} from './ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model';
2020
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
2121
import { DYNAMIC_FORM_CONTROL_TYPE_TAG } from './ds-dynamic-form-ui/models/tag/dynamic-tag.model';
2222
import { RowParser } from './parsers/row-parser';
@@ -26,6 +26,7 @@ import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-inpu
2626
import { FormFieldMetadataValueObject } from './models/form-field-metadata-value.model';
2727
import { isNgbDateStruct } from '../../date.util';
2828
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-ui/ds-dynamic-form-constants';
29+
import { CONCAT_GROUP_SUFFIX, DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
2930

3031
@Injectable()
3132
export class FormBuilderService extends DynamicFormService {
@@ -54,6 +55,13 @@ export class FormBuilderService extends DynamicFormService {
5455
break;
5556
}
5657

58+
if (this.isConcatGroup(controlModel)) {
59+
if (controlModel.id.match(new RegExp(findId + CONCAT_GROUP_SUFFIX + `_\\d+$`))) {
60+
result = (controlModel as DynamicConcatModel).group[0];
61+
break;
62+
}
63+
}
64+
5765
if (this.isGroup(controlModel)) {
5866
findByIdFn(findId, (controlModel as DynamicFormGroupModel).group, findArrayIndex);
5967
}
@@ -247,6 +255,10 @@ export class FormBuilderService extends DynamicFormService {
247255
return model && ((model as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP && (model as any).isCustomGroup === true);
248256
}
249257

258+
isConcatGroup(model: DynamicFormControlModel): boolean {
259+
return this.isCustomGroup(model) && (model.id.indexOf(CONCAT_GROUP_SUFFIX) !== -1);
260+
}
261+
250262
isRowGroup(model: DynamicFormControlModel): boolean {
251263
return model && ((model as any).type === DYNAMIC_FORM_CONTROL_TYPE_GROUP && (model as any).isRowGroup === true);
252264
}
@@ -303,4 +315,76 @@ export class FormBuilderService extends DynamicFormService {
303315
return (tempModel.id !== tempModel.name) ? tempModel.name : tempModel.id;
304316
}
305317

318+
/**
319+
* Calculate the metadata list related to the event.
320+
* @param event
321+
*/
322+
getMetadataIdsFromEvent(event: DynamicFormControlEvent): string[] {
323+
324+
let model = event.model;
325+
while (model.parent) {
326+
model = model.parent as any;
327+
}
328+
329+
const iterateControlModels = (findGroupModel: DynamicFormControlModel[], controlModelIndex: number = 0): string[] => {
330+
let iterateResult = Object.create({});
331+
332+
// Iterate over all group's controls
333+
for (const controlModel of findGroupModel) {
334+
335+
if (this.isRowGroup(controlModel) && !this.isCustomOrListGroup(controlModel)) {
336+
iterateResult = mergeWith(iterateResult, iterateControlModels((controlModel as DynamicFormGroupModel).group));
337+
continue;
338+
}
339+
340+
if (this.isGroup(controlModel) && !this.isCustomOrListGroup(controlModel)) {
341+
iterateResult[controlModel.name] = iterateControlModels((controlModel as DynamicFormGroupModel).group);
342+
continue;
343+
}
344+
345+
if (this.isRowArrayGroup(controlModel)) {
346+
for (const arrayItemModel of (controlModel as DynamicRowArrayModel).groups) {
347+
iterateResult = mergeWith(iterateResult, iterateControlModels(arrayItemModel.group, arrayItemModel.index));
348+
}
349+
continue;
350+
}
351+
352+
if (this.isArrayGroup(controlModel)) {
353+
iterateResult[controlModel.name] = [];
354+
for (const arrayItemModel of (controlModel as DynamicFormArrayModel).groups) {
355+
iterateResult[controlModel.name].push(iterateControlModels(arrayItemModel.group, arrayItemModel.index));
356+
}
357+
continue;
358+
}
359+
360+
let controlId;
361+
// Get the field's name
362+
if (this.isQualdropGroup(controlModel)) {
363+
// If is instance of DynamicQualdropModel take the qualdrop id as field's name
364+
controlId = (controlModel as DynamicQualdropModel).qualdropId;
365+
} else {
366+
controlId = controlModel.name;
367+
}
368+
369+
if (this.isRelationGroup(controlModel)) {
370+
const values = (controlModel as DynamicRelationGroupModel).getGroupValue();
371+
values.forEach((groupValue, groupIndex) => {
372+
Object.keys(groupValue).forEach((key) => {
373+
iterateResult[key] = true;
374+
});
375+
});
376+
} else {
377+
iterateResult[controlId] = true;
378+
}
379+
380+
}
381+
382+
return iterateResult;
383+
};
384+
385+
const result = iterateControlModels([model]);
386+
387+
return Object.keys(result);
388+
}
389+
306390
}

src/app/shared/form/form.actions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { type } from '../ngrx/type';
1313
export const FormActionTypes = {
1414
FORM_INIT: type('dspace/form/FORM_INIT'),
1515
FORM_CHANGE: type('dspace/form/FORM_CHANGE'),
16+
FORM_ADD_TOUCHED: type('dspace/form/FORM_ADD_TOUCHED'),
1617
FORM_REMOVE: type('dspace/form/FORM_REMOVE'),
1718
FORM_STATUS_CHANGE: type('dspace/form/FORM_STATUS_CHANGE'),
1819
FORM_ADD_ERROR: type('dspace/form/FORM_ADD_ERROR'),
@@ -52,7 +53,7 @@ export class FormChangeAction implements Action {
5253
};
5354

5455
/**
55-
* Create a new FormInitAction
56+
* Create a new FormChangeAction
5657
*
5758
* @param formId
5859
* the Form's ID
@@ -64,6 +65,26 @@ export class FormChangeAction implements Action {
6465
}
6566
}
6667

68+
export class FormAddTouchedAction implements Action {
69+
type = FormActionTypes.FORM_ADD_TOUCHED;
70+
payload: {
71+
formId: string;
72+
touched: string[];
73+
};
74+
75+
/**
76+
* Create a new FormAddTouchedAction
77+
*
78+
* @param formId
79+
* the Form's ID
80+
* @param touched
81+
* the array containing new touched fields
82+
*/
83+
constructor(formId: string, touched: string[]) {
84+
this.payload = {formId, touched};
85+
}
86+
}
87+
6788
export class FormRemoveAction implements Action {
6889
type = FormActionTypes.FORM_REMOVE;
6990
payload: {
@@ -147,6 +168,7 @@ export class FormClearErrorsAction implements Action {
147168
*/
148169
export type FormAction = FormInitAction
149170
| FormChangeAction
171+
| FormAddTouchedAction
150172
| FormRemoveAction
151173
| FormStatusChangeAction
152174
| FormAddError

src/app/shared/form/form.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<button type="button" class="btn btn-secondary"
1919
[disabled]="isItemReadOnly(context, index)"
2020
(click)="insertItem($event, group.context, group.index)">
21-
<i class="fas fa-plus" aria-hidden="true"></i>
21+
<span aria-label="Add">{{'form.add' | translate}}</span>
2222
</button>
2323
</div>
2424
</div>

src/app/shared/form/form.component.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ function init() {
119119
dc_identifier_issn: null
120120
},
121121
valid: false,
122-
errors: []
122+
errors: [],
123+
touched: {}
123124
}
124125
};
125126

src/app/shared/form/form.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export class FormComponent implements OnDestroy, OnInit {
253253
}
254254

255255
onFocus(event: DynamicFormControlEvent): void {
256+
this.formService.setTouched(this.formId, this.formModel, event);
256257
this.focus.emit(event);
257258
}
258259

0 commit comments

Comments
 (0)