[ADF-1312] form validation enhancements (#2180)

* validation api enhancements

- changing 'required' causes re-validation of the form
- get field by id

* allow binding field validators from html

* demo validator

* documentation updates

* fix after rebase

* markdown fixes

* markdown linter settings for workspace config (vs code)

* restore material theme
This commit is contained in:
Denys Vuika
2017-08-07 18:41:17 +01:00
committed by Mario Romano
parent 6c1a758561
commit 3d65b49af7
16 changed files with 292 additions and 44 deletions

View File

@@ -22,7 +22,7 @@ import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service';
import { NodeService } from './../services/node.service';
import { ContentLinkModel } from './widgets/core/content-link.model';
import { FormFieldModel, FormModel, FormOutcomeEvent, FormOutcomeModel, FormValues } from './widgets/core/index';
import { FormFieldModel, FormModel, FormOutcomeEvent, FormOutcomeModel, FormValues, FormFieldValidator } from './widgets/core/index';
import { WidgetVisibilityService } from './../services/widget-visibility.service';
@@ -90,6 +90,9 @@ export class FormComponent implements OnInit, OnChanges {
@Input()
showValidationIcon: boolean = true;
@Input()
fieldValidators: FormFieldValidator[] = [];
@Output()
formSaved: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@@ -307,16 +310,16 @@ export class FormComponent implements OnInit, OnChanges {
this.formService
.getTaskForm(taskId)
.subscribe(
form => {
this.form = new FormModel(form, this.data, this.readOnly, this.formService);
this.onFormLoaded(this.form);
resolve(this.form);
},
error => {
this.handleError(error);
// reject(error);
resolve(null);
}
form => {
this.form = this.parseForm(form);
this.onFormLoaded(this.form);
resolve(this.form);
},
error => {
this.handleError(error);
// reject(error);
resolve(null);
}
);
});
});
@@ -396,6 +399,10 @@ export class FormComponent implements OnInit, OnChanges {
if (!json.fields) {
form.outcomes = this.getFormDefinitionOutcomes(form);
}
if (this.fieldValidators && this.fieldValidators.length > 0) {
console.log('Applying custom field validators');
form.fieldValidators = this.fieldValidators;
}
return form;
}
return null;
@@ -419,7 +426,7 @@ export class FormComponent implements OnInit, OnChanges {
}
private refreshFormData() {
this.form = new FormModel(this.form.json, this.data, this.readOnly, this.formService);
this.form = this.parseForm(this.form.json);
this.onFormLoaded(this.form);
this.onFormDataRefreshed(this.form);
}

View File

@@ -355,3 +355,16 @@ export class RegExFieldValidator implements FormFieldValidator {
}
}
export const FORM_FIELD_VALIDATORS = [
new RequiredFieldValidator(),
new NumberFieldValidator(),
new MinLengthFieldValidator(),
new MaxLengthFieldValidator(),
new MinValueFieldValidator(),
new MaxValueFieldValidator(),
new RegExFieldValidator(),
new DateFieldValidator(),
new MinDateFieldValidator(),
new MaxDateFieldValidator()
];

View File

@@ -32,6 +32,7 @@ export class FormFieldModel extends FormWidgetModel {
private _value: string;
private _readOnly: boolean = false;
private _isValid: boolean = true;
private _required: boolean = false;
readonly defaultDateFormat: string = 'D-M-YYYY';
@@ -40,7 +41,6 @@ export class FormFieldModel extends FormWidgetModel {
id: string;
name: string;
type: string;
required: boolean;
overrideId: boolean;
tab: string;
rowspan: number = 1;
@@ -99,6 +99,15 @@ export class FormFieldModel extends FormWidgetModel {
this.updateForm();
}
get required(): boolean {
return this._required;
}
set required(value: boolean) {
this._required = value;
this.updateForm();
}
get isValid(): boolean {
return this._isValid;
}
@@ -126,7 +135,7 @@ export class FormFieldModel extends FormWidgetModel {
this.id = json.id;
this.name = json.name;
this.type = json.type;
this.required = <boolean> json.required;
this._required = <boolean> json.required;
this._readOnly = <boolean> json.readOnly || json.type === 'readonly';
this.overrideId = <boolean> json.overrideId;
this.tab = json.tab;

View File

@@ -20,6 +20,8 @@ import { ValidateFormEvent } from './../../../events/validate-form.event';
import { FormService } from './../../../services/form.service';
import { ContainerModel } from './container.model';
import { FormFieldTypes } from './form-field-types';
import { FORM_FIELD_VALIDATORS, FormFieldValidator } from './form-field-validator';
import { FormFieldModel } from './form-field.model';
import { FormOutcomeModel } from './form-outcome.model';
import { FormModel } from './form.model';
import { TabModel } from './tab.model';
@@ -381,4 +383,67 @@ describe('FormModel', () => {
expect(field.validate).not.toHaveBeenCalled();
expect(form.validateForm).not.toHaveBeenCalled();
});
it('should get field by id', () => {
const form = new FormModel({}, null, false, formService);
const field = { id: 'field1' };
spyOn(form, 'getFormFields').and.returnValue([field]);
const result = form.getFieldById('field1');
expect(result).toBe(field);
});
it('should use custom field validator', () => {
const form = new FormModel({}, null, false, formService);
const testField = new FormFieldModel(form, {
id: 'test-field-1'
});
spyOn(form, 'getFormFields').and.returnValue([testField]);
let validator = <FormFieldValidator> {
isSupported(field: FormFieldModel): boolean {
return true;
},
validate(field: FormFieldModel): boolean {
return true;
}
};
spyOn(validator, 'validate').and.callThrough();
form.fieldValidators = [validator];
form.validateForm();
expect(validator.validate).toHaveBeenCalledWith(testField);
});
it('should re-validate the field when required attribute changes', () => {
const form = new FormModel({}, null, false, formService);
const testField = new FormFieldModel(form, {
id: 'test-field-1',
required: false
});
spyOn(form, 'getFormFields').and.returnValue([testField]);
spyOn(form, 'onFormFieldChanged').and.callThrough();
spyOn(form, 'validateField').and.callThrough();
testField.required = true;
expect(testField.required).toBeTruthy();
expect(form.onFormFieldChanged).toHaveBeenCalledWith(testField);
expect(form.validateField).toHaveBeenCalledWith(testField);
});
it('should not change default validators export', () => {
const form = new FormModel({}, null, false, formService);
const defaultLength = FORM_FIELD_VALIDATORS.length;
expect(form.fieldValidators.length).toBe(defaultLength);
form.fieldValidators.push(<any> {});
expect(form.fieldValidators.length).toBe(defaultLength + 1);
expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength);
});
});

View File

@@ -29,17 +29,8 @@ import { FormWidgetModel, FormWidgetModelCache } from './form-widget.model';
import { TabModel } from './tab.model';
import {
DateFieldValidator,
FormFieldValidator,
MaxDateFieldValidator,
MaxLengthFieldValidator,
MaxValueFieldValidator,
MinDateFieldValidator,
MinLengthFieldValidator,
MinValueFieldValidator,
NumberFieldValidator,
RegExFieldValidator,
RequiredFieldValidator
FORM_FIELD_VALIDATORS,
FormFieldValidator
} from './form-field-validator';
export class FormModel {
@@ -67,7 +58,7 @@ export class FormModel {
fields: FormWidgetModel[] = [];
outcomes: FormOutcomeModel[] = [];
customFieldTemplates: FormFieldTemplates = {};
fieldValidators: FormFieldValidator[] = [];
fieldValidators: FormFieldValidator[] = [...FORM_FIELD_VALIDATORS];
readonly selectedOutcome: string;
values: FormValues = {};
@@ -138,19 +129,6 @@ export class FormModel {
}
}
this.fieldValidators = [
new RequiredFieldValidator(),
new NumberFieldValidator(),
new MinLengthFieldValidator(),
new MaxLengthFieldValidator(),
new MinValueFieldValidator(),
new MaxValueFieldValidator(),
new RegExFieldValidator(),
new DateFieldValidator(),
new MinDateFieldValidator(),
new MaxDateFieldValidator()
];
this.validateForm();
}
@@ -161,6 +139,10 @@ export class FormModel {
}
}
getFieldById(fieldId: string): FormFieldModel {
return this.getFormFields().find(field => field.id === fieldId);
}
// TODO: consider evaluating and caching once the form is loaded
getFormFields(): FormFieldModel[] {
let result: FormFieldModel[] = [];