From 3d65b49af7487b2bce52991669a57788a173ea12 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Mon, 7 Aug 2017 18:41:17 +0100 Subject: [PATCH] [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 --- .vscode/settings.json | 12 +- .../activiti/activiti-demo.component.html | 1 + .../activiti/activiti-demo.component.ts | 13 +- .../activiti/demo-field-validator.ts | 36 ++++++ ng2-components/ng2-activiti-form/README.md | 111 ++++++++++++++++++ .../docs/assets/demo-validator.png | Bin 0 -> 26571 bytes ng2-components/ng2-activiti-form/index.ts | 1 + .../src/components/form.component.ts | 31 +++-- .../widgets/core/form-field-validator.ts | 13 ++ .../widgets/core/form-field.model.ts | 13 +- .../widgets/core/form.model.spec.ts | 65 ++++++++++ .../src/components/widgets/core/form.model.ts | 32 ++--- .../ng2-activiti-tasklist/README.md | 1 + .../components/task-details.component.html | 1 + .../src/components/task-details.component.ts | 5 +- .../src/components/task-header.component.ts | 1 - 16 files changed, 292 insertions(+), 44 deletions(-) create mode 100644 demo-shell-ng2/app/components/activiti/demo-field-validator.ts create mode 100644 ng2-components/ng2-activiti-form/docs/assets/demo-validator.png diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f5f316821..f32d94f698 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,15 @@ "**/.happypack": true }, "editor.renderIndentGuides": true, - "tslint.configFile": "ng2-components/tslint.json" + "tslint.configFile": "ng2-components/tslint.json", + "markdownlint.config": { + "MD032": false, + "MD004": false, + "MD024": false, + "MD009": false, + "MD013": false, + "MD036": false, + "MD033" : false, + "MD031" : false + } } diff --git a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html index c0a5afab2b..022f38e635 100644 --- a/demo-shell-ng2/app/components/activiti/activiti-demo.component.html +++ b/demo-shell-ng2/app/components/activiti/activiti-demo.component.html @@ -68,6 +68,7 @@ { console.log('Event fired:' + event.type); console.log('Event Target:' + event.target); }); - + */ } ngOnInit() { diff --git a/demo-shell-ng2/app/components/activiti/demo-field-validator.ts b/demo-shell-ng2/app/components/activiti/demo-field-validator.ts new file mode 100644 index 0000000000..3d0c391afa --- /dev/null +++ b/demo-shell-ng2/app/components/activiti/demo-field-validator.ts @@ -0,0 +1,36 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FormFieldModel, FormFieldTypes, FormFieldValidator } from 'ng2-activiti-form'; + +export class DemoFieldValidator implements FormFieldValidator { + + isSupported(field: FormFieldModel): boolean { + return field && field.type === FormFieldTypes.TEXT; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field)) { + if (field.value && field.value.toLowerCase() === 'admin') { + field.validationSummary = 'Sorry, the value cannot be "admin".'; + return false; + } + } + return true; + } + +} diff --git a/ng2-components/ng2-activiti-form/README.md b/ng2-components/ng2-activiti-form/README.md index 9ef77fe58b..715a512212 100644 --- a/ng2-components/ng2-activiti-form/README.md +++ b/ng2-components/ng2-activiti-form/README.md @@ -188,6 +188,117 @@ If needed, you can completely redefine the set of validators used by the form. All changes to `fieldValidators` collection are automatically applied to all the further validation cycles. +##### Custom set of validators + +You can provide your own set of field validators based on either custom validator instances, or a mixture of default and custom ones. + +```html + +``` + +The Form component exposes a special `FORM_FIELD_VALIDATORS` constant that allows you get a quick access to all system validator instances. + +```ts +import { FORM_FIELD_VALIDATORS } from 'ng2-activiti-form'; + +@Component({...}) +export class AppComponent { + + fieldValidators = [ + // default set of ADF validators if needed + ...FORM_FIELD_VALIDATORS, + + // custom validators + new MyValidator1(), + new MyValidator2() + ]; + +} +``` + +##### Custom validator example + +A form field validator must implement the "FormFieldValidator" interface: + +```ts +export interface FormFieldValidator { + + isSupported(field: FormFieldModel): boolean; + validate(field: FormFieldModel): boolean; + +} +``` + +There might be many different validators used for various field types and purposes, +so the validation layer needs every validator instance to support "isSupported" call. + +It is up to validator to declare support for a form field. +If you want to check field types the [FormFieldTypes](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-types.ts) class can help you with the predefined constants and helper methods. + +In addition every validator has access to all underlying APIs of the [FormFieldModel](https://github.com/Alfresco/alfresco-ng2-components/blob/master/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts), +including the reference to the Form instance and so other form fields. + +Below is a source code for a demo validator that is executed for all the "TEXT" fields, and ensures the value is not "admin", otherwise the `field.validationSummary` value is set to an error. + +```ts +import { FormFieldModel, FormFieldTypes, FormFieldValidator } from 'ng2-activiti-form'; + +export class DemoFieldValidator implements FormFieldValidator { + + isSupported(field: FormFieldModel): boolean { + return field && field.type === FormFieldTypes.TEXT; + } + + validate(field: FormFieldModel): boolean { + if (this.isSupported(field)) { + if (field.value && field.value.toLowerCase() === 'admin') { + field.validationSummary = 'Sorry, the value cannot be "admin".'; + return false; + } + } + return true; + } + +} +``` + +Your component can extend the default validation set instead of replacing it entirely. +In the example below we redefine a default validation set with an additional "DemoFieldValidator": + +```ts +import { DemoFieldValidator } from './demo-field-validator'; + +@Component({...}) +export class AppComponent { + + fieldValidators = [ + ...FORM_FIELD_VALIDATORS, + new DemoFieldValidator() + ]; + +} +``` + +You can now use the 'fieldValidators' property with the Form or Task Details components to assign custom validator set for the underlying Form Model: + +```html + + + + + + + +``` + +Now if you run the application and try to enter "admin" in one of the text fields (either optional or required), you should see the following error: + +![](docs/assets/demo-validator.png) + ### Advanced properties The following properties are for complex customisation purposes: diff --git a/ng2-components/ng2-activiti-form/docs/assets/demo-validator.png b/ng2-components/ng2-activiti-form/docs/assets/demo-validator.png new file mode 100644 index 0000000000000000000000000000000000000000..e06d00b0c6a8d5e1c8f3b288c584add711320b24 GIT binary patch literal 26571 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%0Di1@8uakxF|Xq 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
x-@yV&Bs%Q(ZCOw4UH6zZ(+d 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 literal 0 HcmV?d00001 diff --git a/ng2-components/ng2-activiti-form/index.ts b/ng2-components/ng2-activiti-form/index.ts index 293ea0ab3f..2bd2ebe95f 100644 --- a/ng2-components/ng2-activiti-form/index.ts +++ b/ng2-components/ng2-activiti-form/index.ts @@ -47,6 +47,7 @@ export * from './src/services/ecm-model.service'; export * from './src/services/node.service'; export * from './src/services/form-rendering.service'; export * from './src/events/index'; +export { FORM_FIELD_VALIDATORS } from './src/components/widgets/core/form-field-validator'; // Old deprecated import import {FormComponent as ActivitiForm } from './src/components/form.component'; diff --git a/ng2-components/ng2-activiti-form/src/components/form.component.ts b/ng2-components/ng2-activiti-form/src/components/form.component.ts index 15ac9521eb..8ab15de872 100644 --- a/ng2-components/ng2-activiti-form/src/components/form.component.ts +++ b/ng2-components/ng2-activiti-form/src/components/form.component.ts @@ -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 = new EventEmitter(); @@ -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); } diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts index a1888cad44..178f760e60 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field-validator.ts @@ -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() +]; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts index 1068bab70f..2037aef007 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form-field.model.ts @@ -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 = json.required; + this._required = json.required; this._readOnly = json.readOnly || json.type === 'readonly'; this.overrideId = json.overrideId; this.tab = json.tab; diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts index 68b7e10f0f..3db8de714a 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.spec.ts @@ -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 = { + 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( {}); + + expect(form.fieldValidators.length).toBe(defaultLength + 1); + expect(FORM_FIELD_VALIDATORS.length).toBe(defaultLength); + }); }); diff --git a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts index 2d4d90a7b7..f55fb0363c 100644 --- a/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts +++ b/ng2-components/ng2-activiti-form/src/components/widgets/core/form.model.ts @@ -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[] = []; diff --git a/ng2-components/ng2-activiti-tasklist/README.md b/ng2-components/ng2-activiti-tasklist/README.md index 4c81099c11..0338b16a17 100644 --- a/ng2-components/ng2-activiti-tasklist/README.md +++ b/ng2-components/ng2-activiti-tasklist/README.md @@ -169,6 +169,7 @@ The component shows the details of the task id passed in input | showInvolvePeople | boolean | true | Toggle `Involve People` feature for Header component | | showComments | boolean | true | Toggle `Comments` feature for Header component | | showChecklist | boolean | true | Toggle `Checklist` feature for Header component | +| fieldValidators | FormFieldValidator[] | [] | Field validators for use with the form. | ### Events diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.html b/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.html index 69afe9b615..e373501842 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.html @@ -40,6 +40,7 @@ [disableCompleteButton]="!isAssignedToMe()" [showSaveButton]="showFormSaveButton" [readOnly]="readOnlyForm" + [fieldValidators]="fieldValidators" (formSaved)='onFormSaved($event)' (formCompleted)='onFormCompleted($event)' (formContentClicked)='onFormContentClick($event)' diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.ts index 2a2f5d840b..7919465b34 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/task-details.component.ts @@ -26,7 +26,7 @@ import { Component, TemplateRef, ViewChild } from '@angular/core'; -import { ContentLinkModel, FormModel, FormOutcomeEvent } from 'ng2-activiti-form'; +import { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } from 'ng2-activiti-form'; import { AlfrescoAuthenticationService, AlfrescoTranslationService, CardViewUpdateService, ClickNotification, UpdateNotification } from 'ng2-alfresco-core'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; @@ -96,6 +96,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges { @Input() peopleIconImageUrl: string = require('../assets/images/user.jpg'); + @Input() + fieldValidators: FormFieldValidator[] = []; + @Output() formSaved: EventEmitter = new EventEmitter(); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts index 4966184699..006a3bbf9b 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts @@ -48,7 +48,6 @@ export class TaskHeaderComponent implements OnChanges { } ngOnChanges(changes: SimpleChanges) { - console.log('change van:', changes, this.taskDetails); this.refreshData(); }