From 61de0618ea0cf58571ca9f60058e3e08b4be6f92 Mon Sep 17 00:00:00 2001 From: Clemente Raposo Date: Thu, 31 Dec 2020 15:08:27 +0000 Subject: [PATCH] Restructure front end filters setup - Use FieldManager - Change ValidationManager to support save and filter validation - Use reactive forms formControl in varchar filter - Add null checks to datetime formatter - Add field type to criteria - Set form control value on field init - display messages for edit or filter modes - Validate filter input - Display warning messages when not valid - Adjust karma / jasmine tests --- core/app/fields/base/base-field.component.ts | 6 +- core/app/fields/field.component.spec.ts | 2 +- core/app/fields/field.component.ts | 2 +- .../templates/filter/filter.component.html | 5 +- .../templates/filter/filter.component.spec.ts | 13 +- .../templates/filter/filter.component.ts | 18 ++- .../varchar/templates/filter/filter.module.ts | 5 +- .../metadata/list.metadata.model.ts | 2 + .../app/src/app-common/record/record.model.ts | 2 +- .../views/list/search-criteria.model.ts | 1 + .../list-filter/list-filter.component.spec.ts | 4 +- .../list-filter/list-filter.component.ts | 94 +++++++++++--- .../datetime/datetime-formatter.service.ts | 12 +- .../services/record/field/field.manager.ts | 76 +++++++++-- .../record/validation/validation.manager.ts | 121 +++++++++++------- 15 files changed, 270 insertions(+), 93 deletions(-) diff --git a/core/app/fields/base/base-field.component.ts b/core/app/fields/base/base-field.component.ts index bda0e7ccf..06df77e57 100644 --- a/core/app/fields/base/base-field.component.ts +++ b/core/app/fields/base/base-field.component.ts @@ -27,11 +27,15 @@ export class BaseFieldComponent implements FieldComponentInterface { newValue = this.typeFormatter.toInternalFormat(this.field.type, newValue); } - this.field.value = newValue; + this.setFieldValue(newValue); })); } } + protected setFieldValue(newValue): void { + this.field.value = newValue; + } + protected unsubscribeAll(): void { this.subs.forEach(sub => sub.unsubscribe()); } diff --git a/core/app/fields/field.component.spec.ts b/core/app/fields/field.component.spec.ts index b11a45ebd..4bb64bfe2 100644 --- a/core/app/fields/field.component.spec.ts +++ b/core/app/fields/field.component.spec.ts @@ -45,7 +45,7 @@ class FieldTestHostComponent { {field: buildField({type: 'varchar', value: 'My Varchar'}), mode: 'list', expected: 'My Varchar'}, {field: buildField({type: 'varchar', value: 'My Varchar'}), mode: 'edit', expected: 'My Varchar'}, { - field: buildField({type: 'varchar', criteria: {values: ['test'], operator: '='}}), + field: buildField({type: 'varchar', value: 'test', criteria: {values: ['test'], operator: '='}}), mode: 'filter', expected: 'test' }, diff --git a/core/app/fields/field.component.ts b/core/app/fields/field.component.ts index 455eb77bd..76533fe64 100644 --- a/core/app/fields/field.component.ts +++ b/core/app/fields/field.component.ts @@ -50,7 +50,7 @@ export class FieldComponent { } isEdit(): boolean { - return this.mode === 'edit'; + return this.mode === 'edit' || this.mode === 'filter'; } getLink(): string { diff --git a/core/app/fields/varchar/templates/filter/filter.component.html b/core/app/fields/varchar/templates/filter/filter.component.html index 23f10e579..56a8d8fd7 100644 --- a/core/app/fields/varchar/templates/filter/filter.component.html +++ b/core/app/fields/varchar/templates/filter/filter.component.html @@ -1 +1,4 @@ - \ No newline at end of file + diff --git a/core/app/fields/varchar/templates/filter/filter.component.spec.ts b/core/app/fields/varchar/templates/filter/filter.component.spec.ts index 022b7d135..8f38e3d10 100644 --- a/core/app/fields/varchar/templates/filter/filter.component.spec.ts +++ b/core/app/fields/varchar/templates/filter/filter.component.spec.ts @@ -1,7 +1,6 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Component} from '@angular/core'; import {VarcharFilterFieldComponent} from './filter.component'; -import {FormsModule} from '@angular/forms'; import {Field} from '@app-common/record/field.model'; import {UserPreferenceStore} from '@store/user-preference/user-preference.store'; import {userPreferenceStoreMock} from '@store/user-preference/user-preference.store.spec.mock'; @@ -12,6 +11,9 @@ import {datetimeFormatterMock} from '@services/formatters/datetime/datetime-form import {DateFormatter} from '@services/formatters/datetime/date-formatter.service'; import {dateFormatterMock} from '@services/formatters/datetime/date-formatter.service.spec.mock'; import {CurrencyFormatter} from '@services/formatters/currency/currency-formatter.service'; +import {VarcharFilterFieldModule} from '@fields/varchar/templates/filter/filter.module'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {FormControl} from '@angular/forms'; @Component({ selector: 'varchar-filter-field-test-host-component', @@ -20,10 +22,12 @@ import {CurrencyFormatter} from '@services/formatters/currency/currency-formatte class VarcharFilterFieldTestHostComponent { field: Field = { type: 'varchar', + value: 'test filter value', criteria: { values: ['test filter value'], operator: '=' - } + }, + formControl: new FormControl('test filter value') }; } @@ -38,7 +42,8 @@ describe('VarcharFilterFieldComponent', () => { VarcharFilterFieldComponent, ], imports: [ - FormsModule + VarcharFilterFieldModule, + BrowserAnimationsModule ], providers: [ {provide: UserPreferenceStore, useValue: userPreferenceStoreMock}, @@ -70,7 +75,7 @@ describe('VarcharFilterFieldComponent', () => { it('should have update input when field changes', async(() => { expect(testHostComponent).toBeTruthy(); - testHostComponent.field.criteria.values = ['New Field value']; + testHostComponent.field.formControl.setValue('New Field value'); testHostFixture.detectChanges(); testHostFixture.whenStable().then(() => { diff --git a/core/app/fields/varchar/templates/filter/filter.component.ts b/core/app/fields/varchar/templates/filter/filter.component.ts index 1814a748e..8849254db 100644 --- a/core/app/fields/varchar/templates/filter/filter.component.ts +++ b/core/app/fields/varchar/templates/filter/filter.component.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, OnDestroy, OnInit} from '@angular/core'; import {BaseFieldComponent} from '@fields/base/base-field.component'; import {DataTypeFormatter} from '@services/formatters/data-type.formatter.service'; @@ -7,23 +7,31 @@ import {DataTypeFormatter} from '@services/formatters/data-type.formatter.servic templateUrl: './filter.component.html', styleUrls: [] }) -export class VarcharFilterFieldComponent extends BaseFieldComponent { +export class VarcharFilterFieldComponent extends BaseFieldComponent implements OnInit, OnDestroy { constructor(protected typeFormatter: DataTypeFormatter) { super(typeFormatter); } - get value(): string { + ngOnInit(): void { let current = ''; if (this.field.criteria.values && this.field.criteria.values.length > 0) { current = this.field.criteria.values[0]; } - return current; + this.field.value = current; + const formattedValue = this.typeFormatter.toUserFormat(this.field.type, current, {mode: 'edit'}); + this.field.formControl.setValue(formattedValue); + + this.subscribeValueChanges(); } - set value(newValue: string) { + ngOnDestroy(): void { + this.unsubscribeAll(); + } + + protected setFieldValue(newValue): void { this.field.value = newValue; this.field.criteria.operator = '='; this.field.criteria.values = [newValue]; diff --git a/core/app/fields/varchar/templates/filter/filter.module.ts b/core/app/fields/varchar/templates/filter/filter.module.ts index 33647c4f7..e127e5570 100644 --- a/core/app/fields/varchar/templates/filter/filter.module.ts +++ b/core/app/fields/varchar/templates/filter/filter.module.ts @@ -3,7 +3,7 @@ import {CommonModule} from '@angular/common'; import {AppManagerModule} from '@base/app-manager/app-manager.module'; import {VarcharFilterFieldComponent} from './filter.component'; -import {FormsModule} from '@angular/forms'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; @NgModule({ declarations: [VarcharFilterFieldComponent], @@ -11,7 +11,8 @@ import {FormsModule} from '@angular/forms'; imports: [ CommonModule, AppManagerModule.forChild(VarcharFilterFieldComponent), - FormsModule + FormsModule, + ReactiveFormsModule ] }) export class VarcharFilterFieldModule { diff --git a/core/app/src/app-common/metadata/list.metadata.model.ts b/core/app/src/app-common/metadata/list.metadata.model.ts index 2fe3fe0b6..5e1fa87b4 100644 --- a/core/app/src/app-common/metadata/list.metadata.model.ts +++ b/core/app/src/app-common/metadata/list.metadata.model.ts @@ -3,6 +3,7 @@ import {BulkActionsMap} from '@app-common/actions/bulk-action.model'; import {LineAction} from '@app-common/actions/line-action.model'; import {ChartTypesMap} from '@app-common/containers/chart/chart.model'; import {WidgetMetadata} from '@app-common/metadata/widget.metadata'; +import {FieldDefinition} from '@app-common/record/field.model'; export interface ListViewMeta { fields: ColumnDefinition[]; @@ -33,6 +34,7 @@ export interface SearchMetaField { label?: string; default?: boolean; options?: string; + fieldDefinition?: FieldDefinition; } export interface SearchMeta { diff --git a/core/app/src/app-common/record/record.model.ts b/core/app/src/app-common/record/record.model.ts index af37c19a9..a8fa5b286 100644 --- a/core/app/src/app-common/record/record.model.ts +++ b/core/app/src/app-common/record/record.model.ts @@ -7,7 +7,7 @@ export interface AttributeMap { export interface Record { id?: string; - type: string; + type?: string; module: string; attributes: AttributeMap; fields?: FieldMap; diff --git a/core/app/src/app-common/views/list/search-criteria.model.ts b/core/app/src/app-common/views/list/search-criteria.model.ts index bff662bda..b95386443 100644 --- a/core/app/src/app-common/views/list/search-criteria.model.ts +++ b/core/app/src/app-common/views/list/search-criteria.model.ts @@ -1,5 +1,6 @@ export interface SearchCriteriaFieldFilter { field?: string; + fieldType?: string; operator: string; values?: string[]; start?: string; diff --git a/core/app/src/components/list-filter/list-filter.component.spec.ts b/core/app/src/components/list-filter/list-filter.component.spec.ts index ecd1fbf88..bfbf9f91e 100644 --- a/core/app/src/components/list-filter/list-filter.component.spec.ts +++ b/core/app/src/components/list-filter/list-filter.component.spec.ts @@ -14,6 +14,7 @@ import {LanguageStore} from '@store/language/language.store'; import {languageStoreMock} from '@store/language/language.store.spec.mock'; import {RouterTestingModule} from '@angular/router/testing'; import {ApolloTestingModule} from 'apollo-angular/testing'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; describe('ListFilterComponent', () => { let testHostComponent: ListFilterComponent; @@ -31,7 +32,8 @@ describe('ListFilterComponent', () => { DropdownButtonModule, FieldGridModule, RouterTestingModule, - ApolloTestingModule + ApolloTestingModule, + BrowserAnimationsModule ], providers: [ {provide: ListViewStore, useValue: listviewStoreMock}, diff --git a/core/app/src/components/list-filter/list-filter.component.ts b/core/app/src/components/list-filter/list-filter.component.ts index 53cc27ab2..c9b7b310c 100644 --- a/core/app/src/components/list-filter/list-filter.component.ts +++ b/core/app/src/components/list-filter/list-filter.component.ts @@ -5,10 +5,15 @@ import {DropdownButtonInterface} from '@components/dropdown-button/dropdown-butt import {ButtonInterface} from '@components/button/button.model'; import {deepClone} from '@base/app-common/utils/object-utils'; import {combineLatest, Observable} from 'rxjs'; -import {map} from 'rxjs/operators'; -import {Field} from '@app-common/record/field.model'; +import {filter, map, startWith, take} from 'rxjs/operators'; +import {Field, FieldMap} from '@app-common/record/field.model'; import {SearchCriteria, SearchCriteriaFieldFilter} from '@app-common/views/list/search-criteria.model'; import {Filter, SearchMetaField} from '@app-common/metadata/list.metadata.model'; +import {FieldManager} from '@services/record/field/field.manager'; +import {ViewFieldDefinition} from '@app-common/metadata/metadata.model'; +import {Record} from '@app-common/record/record.model'; +import {AbstractControl, FormGroup} from '@angular/forms'; +import {MessageService} from '@services/message/message.service'; export interface FilterDataSource { getFilter(): Observable; @@ -34,8 +39,14 @@ export class ListFilterComponent implements OnInit { searchCriteria: SearchCriteria; vm$: Observable; + private record: Record; - constructor(protected listStore: ListViewStore, protected language: LanguageStore) { + constructor( + protected listStore: ListViewStore, + protected language: LanguageStore, + protected fieldManager: FieldManager, + protected message: MessageService + ) { this.vm$ = combineLatest([listStore.criteria$, listStore.metadata$]).pipe( map(([criteria, metadata]) => { @@ -51,6 +62,11 @@ export class ListFilterComponent implements OnInit { this.reset(); + this.record = { + module: this.listStore.getModuleName(), + attributes: {} + } as Record; + this.initFields(); this.initGridButtons(); this.initHeaderButtons(); @@ -72,15 +88,23 @@ export class ListFilterComponent implements OnInit { } const searchFields = searchMeta.layout[type]; + const fields = {} as FieldMap; + const formControls = {} as { [key: string]: AbstractControl }; Object.keys(searchFields).forEach(key => { const name = searchFields[key].name; + + fields[name] = this.buildField(searchFields[key], languages, searchCriteria); + formControls[name] = fields[name].formControl; + if (name.includes('_only')) { - this.special.push(this.buildField(searchFields[key], languages, searchCriteria)); + this.special.push(fields[name]); } else { - this.fields.push(this.buildField(searchFields[key], languages, searchCriteria)); + this.fields.push(fields[name]); } }); + + this.record.formGroup = new FormGroup(formControls); } protected reset(): void { @@ -128,21 +152,34 @@ export class ListFilterComponent implements OnInit { } protected buildField(searchField: SearchMetaField, languages: LanguageStrings, searchCriteria: SearchCriteria): Field { - const module = this.listStore.appState.module; - const fieldName = searchField.name; - this.searchCriteria.filters[fieldName] = this.initFieldFilter(searchCriteria, fieldName); + const type = searchField.type; + this.searchCriteria.filters[fieldName] = this.initFieldFilter(searchCriteria, fieldName, type); - return { - type: 'varchar', - value: '', + const definition = { name: searchField.name, - label: this.language.getFieldLabel(searchField.label, module, languages), - criteria: this.searchCriteria.filters[fieldName] - } as Field; + label: searchField.label, + type, + fieldDefinition: {} + } as ViewFieldDefinition; + + if (searchField.fieldDefinition) { + definition.fieldDefinition = searchField.fieldDefinition; + } + + if (type === 'bool' || type === 'boolean') { + definition.fieldDefinition.options = 'dom_int_bool'; + } + + + const field = this.fieldManager.buildFilterField(this.record, definition, this.language); + + field.criteria = this.searchCriteria.filters[fieldName]; + + return field; } - protected initFieldFilter(searchCriteria: SearchCriteria, fieldName: string): SearchCriteriaFieldFilter { + protected initFieldFilter(searchCriteria: SearchCriteria, fieldName: string, fieldType: string): SearchCriteriaFieldFilter { let fieldCriteria: SearchCriteriaFieldFilter; if (searchCriteria.filters[fieldName]) { @@ -150,8 +187,9 @@ export class ListFilterComponent implements OnInit { } else { fieldCriteria = { field: fieldName, + fieldType, operator: '', - values: [], + values: [] }; } @@ -159,8 +197,28 @@ export class ListFilterComponent implements OnInit { } protected applyFilter(): void { - this.listStore.showFilters = false; - this.listStore.updateSearchCriteria(this.searchCriteria); + this.validate().pipe(take(1)).subscribe(valid => { + + if (valid) { + this.listStore.showFilters = false; + this.listStore.updateSearchCriteria(this.searchCriteria); + return; + } + + this.message.addWarningMessageByKey('LBL_VALIDATION_ERRORS'); + }); + + } + + protected validate(): Observable { + + this.record.formGroup.markAllAsTouched(); + return this.record.formGroup.statusChanges.pipe( + startWith(this.record.formGroup.status), + filter(status => status !== 'PENDING'), + take(1), + map(status => status === 'VALID') + ); } protected clearFilter(): void { diff --git a/core/app/src/services/formatters/datetime/datetime-formatter.service.ts b/core/app/src/services/formatters/datetime/datetime-formatter.service.ts index f643686c5..5813ac9ae 100644 --- a/core/app/src/services/formatters/datetime/datetime-formatter.service.ts +++ b/core/app/src/services/formatters/datetime/datetime-formatter.service.ts @@ -91,7 +91,17 @@ export class DatetimeFormatter implements Formatter { } toUserFormat(dateString: string): string { - return formatDate(dateString, this.getUserFormat(), this.locale); + if (!dateString) { + return ''; + } + + const dateTime = this.toDateTime(dateString); + + if (!dateTime.isValid) { + return ''; + } + + return formatDate(dateTime.toJSDate(), this.getUserFormat(), this.locale); } toInternalFormat(dateString: string): string { diff --git a/core/app/src/services/record/field/field.manager.ts b/core/app/src/services/record/field/field.manager.ts index a00b07481..0efdb652d 100644 --- a/core/app/src/services/record/field/field.manager.ts +++ b/core/app/src/services/record/field/field.manager.ts @@ -1,7 +1,7 @@ import {Record} from '@app-common/record/record.model'; import {ViewFieldDefinition} from '@app-common/metadata/metadata.model'; import {LanguageStore} from '@store/language/language.store'; -import {FormControl} from '@angular/forms'; +import {AsyncValidatorFn, FormControl, ValidatorFn} from '@angular/forms'; import {Field, FieldDefinition} from '@app-common/record/field.model'; import {Injectable} from '@angular/core'; import {ValidationManager} from '@services/record/validation/validation.manager'; @@ -28,14 +28,40 @@ export class FieldManager { public buildField(record: Record, viewField: ViewFieldDefinition, language: LanguageStore = null): Field { const definition = (viewField && viewField.fieldDefinition) || {} as FieldDefinition; + const {value, valueList} = this.parseValue(viewField, definition, record); + const {validators, asyncValidators} = this.getValidators(record, viewField); + + return this.setupField(viewField, value, valueList, record, definition, validators, asyncValidators, language); + } + + public buildFilterField(record: Record, viewField: ViewFieldDefinition, language: LanguageStore = null): Field { + + const definition = (viewField && viewField.fieldDefinition) || {} as FieldDefinition; + const {value, valueList} = this.parseValue(viewField, definition, record); + const {validators, asyncValidators} = this.getFilterValidators(record, viewField); + + return this.setupField(viewField, value, valueList, record, definition, validators, asyncValidators, language); + } + + public getFieldLabel(label: string, module: string, language: LanguageStore): string { + const languages = language.getLanguageStrings(); + return language.getFieldLabel(label, module, languages); + } + + protected parseValue( + viewField: ViewFieldDefinition, + definition: FieldDefinition, + record: Record + ): { value: string; valueList: string[] } { + const type = (viewField && viewField.type) || ''; const source = (definition && definition.source) || ''; const rname = (definition && definition.rname) || 'name'; const viewName = viewField.name || ''; - let value; - let valueList = null; + let value: string; + let valueList: string[] = null; - if (!viewName) { + if (!viewName || !record.attributes[viewName]) { value = ''; } else if (type === 'relate' && source === 'non-db' && rname !== '') { value = record.attributes[viewName][rname]; @@ -48,8 +74,40 @@ export class FieldManager { value = null; } - const validators = this.validationManager.getValidations(record.module, viewField, record); - const asyncValidators = this.validationManager.getAsyncValidations(record.module, viewField, record); + return {value, valueList}; + } + + protected getValidators( + record: Record, + viewField: ViewFieldDefinition + ): { validators: ValidatorFn[]; asyncValidators: AsyncValidatorFn[] } { + + const validators = this.validationManager.getSaveValidations(record.module, viewField, record); + const asyncValidators = this.validationManager.getAsyncSaveValidations(record.module, viewField, record); + return {validators, asyncValidators}; + } + + protected getFilterValidators( + record: Record, + viewField: ViewFieldDefinition + ): { validators: ValidatorFn[]; asyncValidators: AsyncValidatorFn[] } { + + const validators = this.validationManager.getFilterValidations(record.module, viewField, record); + const asyncValidators: AsyncValidatorFn[] = []; + + return {validators, asyncValidators}; + } + + protected setupField( + viewField: ViewFieldDefinition, + value: string, + valueList: string[], + record: Record, + definition: FieldDefinition, + validators: ValidatorFn[], + asyncValidators: AsyncValidatorFn[], + language: LanguageStore + ): Field { const formattedValue = this.typeFormatter.toUserFormat(viewField.type, value, {mode: 'edit'}); @@ -73,12 +131,6 @@ export class FieldManager { if (language) { field.label = this.getFieldLabel(viewField.label, record.module, language); } - return field; } - - public getFieldLabel(label: string, module: string, language: LanguageStore): string { - const languages = language.getLanguageStrings(); - return language.getFieldLabel(label, module, languages); - } } diff --git a/core/app/src/services/record/validation/validation.manager.ts b/core/app/src/services/record/validation/validation.manager.ts index 7a77de99e..003a5ccf9 100644 --- a/core/app/src/services/record/validation/validation.manager.ts +++ b/core/app/src/services/record/validation/validation.manager.ts @@ -4,7 +4,7 @@ import {Record} from '@app-common/record/record.model'; import {ViewFieldDefinition} from '@app-common/metadata/metadata.model'; import {AsyncValidatorFn, ValidatorFn} from '@angular/forms'; import {AsyncValidatorInterface} from '@services/record/validation/aync-validator.Interface'; -import {OverridableMap} from '@app-common/types/OverridableMap'; +import {MapEntry, OverridableMap} from '@app-common/types/OverridableMap'; import {RequiredValidator} from '@services/record/validation/validators/required.validator'; import {CurrencyValidator} from '@services/record/validation/validators/currency.validator'; import {DateValidator} from '@services/record/validation/validators/date.validator'; @@ -16,25 +16,32 @@ import {PhoneValidator} from '@services/record/validation/validators/phone.valid import {RangeValidator} from '@services/record/validation/validators/range.validator'; export interface ValidationManagerInterface { - registerValidator(module: string, key: string, validator: ValidatorInterface): void; + registerSaveValidator(module: string, key: string, validator: ValidatorInterface): void; - excludeValidator(module: string, key: string): void; + registerFilterValidator(module: string, key: string, validator: ValidatorInterface): void; - registerAsyncValidator(module: string, key: string, validator: AsyncValidatorInterface): void; + excludeSaveValidator(module: string, key: string): void; - excludeAsyncValidator(module: string, key: string): void; + excludeFilterValidator(module: string, key: string): void; - getValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[]; + registerAsyncSaveValidator(module: string, key: string, validator: AsyncValidatorInterface): void; - getAsyncValidations(module: string, viewField: ViewFieldDefinition, record: Record): AsyncValidatorFn[]; + excludeAsyncSaveValidator(module: string, key: string): void; + + getSaveValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[]; + + getFilterValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[]; + + getAsyncSaveValidations(module: string, viewField: ViewFieldDefinition, record: Record): AsyncValidatorFn[]; } @Injectable({ providedIn: 'root' }) export class ValidationManager implements ValidationManagerInterface { - protected validators: OverridableMap; - protected asyncValidators: OverridableMap; + protected saveValidators: OverridableMap; + protected asyncSaveValidators: OverridableMap; + protected filterValidators: OverridableMap; constructor( protected requiredValidator: RequiredValidator, @@ -48,57 +55,66 @@ export class ValidationManager implements ValidationManagerInterface { protected phoneValidator: PhoneValidator, ) { - this.validators = new OverridableMap(); - this.asyncValidators = new OverridableMap(); + this.saveValidators = new OverridableMap(); + this.asyncSaveValidators = new OverridableMap(); + this.filterValidators = new OverridableMap(); - this.validators.addEntry('default', 'required', requiredValidator); - this.validators.addEntry('default', 'range', rangeValidator); - this.validators.addEntry('default', 'currency', currencyValidator); - this.validators.addEntry('default', 'date', dateValidator); - this.validators.addEntry('default', 'datetime', datetimeValidator); - this.validators.addEntry('default', 'email', emailValidator); - this.validators.addEntry('default', 'float', floatValidator); - this.validators.addEntry('default', 'int', intValidator); - this.validators.addEntry('default', 'phone', phoneValidator); + this.saveValidators.addEntry('default', 'required', requiredValidator); + this.saveValidators.addEntry('default', 'range', rangeValidator); + this.saveValidators.addEntry('default', 'currency', currencyValidator); + this.saveValidators.addEntry('default', 'date', dateValidator); + this.saveValidators.addEntry('default', 'datetime', datetimeValidator); + this.saveValidators.addEntry('default', 'email', emailValidator); + this.saveValidators.addEntry('default', 'float', floatValidator); + this.saveValidators.addEntry('default', 'int', intValidator); + this.saveValidators.addEntry('default', 'phone', phoneValidator); + + this.filterValidators.addEntry('default', 'date', dateValidator); + this.filterValidators.addEntry('default', 'datetime', datetimeValidator); + this.filterValidators.addEntry('default', 'float', floatValidator); + this.filterValidators.addEntry('default', 'currency', currencyValidator); + this.filterValidators.addEntry('default', 'int', intValidator); + this.filterValidators.addEntry('default', 'phone', phoneValidator); } - public registerValidator(module: string, key: string, validator: ValidatorInterface): void { - this.validators.addEntry(module, key, validator); + public registerSaveValidator(module: string, key: string, validator: ValidatorInterface): void { + this.filterValidators.addEntry(module, key, validator); } - public excludeValidator(module: string, key: string): void { - this.validators.excludeEntry(module, key); + public registerFilterValidator(module: string, key: string, validator: ValidatorInterface): void { + this.saveValidators.addEntry(module, key, validator); } - public registerAsyncValidator(module: string, key: string, validator: AsyncValidatorInterface): void { - this.asyncValidators.addEntry(module, key, validator); + public excludeSaveValidator(module: string, key: string): void { + this.saveValidators.excludeEntry(module, key); } - public excludeAsyncValidator(module: string, key: string): void { - this.validators.excludeEntry(module, key); + public excludeFilterValidator(module: string, key: string): void { + this.filterValidators.excludeEntry(module, key); } - public getValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[] { - let validations = []; - - const entries = this.validators.getGroupEntries(module); - - Object.keys(entries).forEach(validatorKey => { - - const validator = entries[validatorKey]; - - if (validator.applies(record, viewField)) { - validations = validations.concat(validator.getValidator(viewField, record)); - } - }); - - return validations; + public registerAsyncSaveValidator(module: string, key: string, validator: AsyncValidatorInterface): void { + this.asyncSaveValidators.addEntry(module, key, validator); } - public getAsyncValidations(module: string, viewField: ViewFieldDefinition, record: Record): AsyncValidatorFn[] { + public excludeAsyncSaveValidator(module: string, key: string): void { + this.saveValidators.excludeEntry(module, key); + } + + public getSaveValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[] { + const entries = this.saveValidators.getGroupEntries(module); + return this.filterValidations(entries, record, viewField); + } + + public getFilterValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[] { + const entries = this.filterValidators.getGroupEntries(module); + return this.filterValidations(entries, record, viewField); + } + + public getAsyncSaveValidations(module: string, viewField: ViewFieldDefinition, record: Record): AsyncValidatorFn[] { const validations = []; - const entries = this.asyncValidators.getGroupEntries(module); + const entries = this.asyncSaveValidators.getGroupEntries(module); Object.keys(entries).forEach(validatorKey => { @@ -111,4 +127,19 @@ export class ValidationManager implements ValidationManagerInterface { return validations; } + + protected filterValidations(entries: MapEntry, record: Record, viewField: ViewFieldDefinition): ValidatorFn[] { + let validations = []; + + Object.keys(entries).forEach(validatorKey => { + + const validator = entries[validatorKey]; + + if (validator.applies(record, viewField)) { + validations = validations.concat(validator.getValidator(viewField, record)); + } + }); + + return validations; + } }