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
This commit is contained in:
Clemente Raposo 2020-12-31 15:08:27 +00:00 committed by Dillon-Brown
parent 0916d14a5c
commit 61de0618ea
15 changed files with 270 additions and 93 deletions

View file

@ -27,11 +27,15 @@ export class BaseFieldComponent implements FieldComponentInterface {
newValue = this.typeFormatter.toInternalFormat(this.field.type, newValue); 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 { protected unsubscribeAll(): void {
this.subs.forEach(sub => sub.unsubscribe()); this.subs.forEach(sub => sub.unsubscribe());
} }

View file

@ -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: 'list', expected: 'My Varchar'},
{field: buildField({type: 'varchar', value: 'My Varchar'}), mode: 'edit', 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', mode: 'filter',
expected: 'test' expected: 'test'
}, },

View file

@ -50,7 +50,7 @@ export class FieldComponent {
} }
isEdit(): boolean { isEdit(): boolean {
return this.mode === 'edit'; return this.mode === 'edit' || this.mode === 'filter';
} }
getLink(): string { getLink(): string {

View file

@ -1 +1,4 @@
<input type="text" [ngClass]="klass" [(ngModel)]="value"> <input [class.is-invalid]="field.formControl.invalid && field.formControl.touched"
[formControl]="field.formControl"
[ngClass]="klass"
type="text">

View file

@ -1,7 +1,6 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {VarcharFilterFieldComponent} from './filter.component'; import {VarcharFilterFieldComponent} from './filter.component';
import {FormsModule} from '@angular/forms';
import {Field} from '@app-common/record/field.model'; import {Field} from '@app-common/record/field.model';
import {UserPreferenceStore} from '@store/user-preference/user-preference.store'; import {UserPreferenceStore} from '@store/user-preference/user-preference.store';
import {userPreferenceStoreMock} from '@store/user-preference/user-preference.store.spec.mock'; 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 {DateFormatter} from '@services/formatters/datetime/date-formatter.service';
import {dateFormatterMock} from '@services/formatters/datetime/date-formatter.service.spec.mock'; import {dateFormatterMock} from '@services/formatters/datetime/date-formatter.service.spec.mock';
import {CurrencyFormatter} from '@services/formatters/currency/currency-formatter.service'; 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({ @Component({
selector: 'varchar-filter-field-test-host-component', selector: 'varchar-filter-field-test-host-component',
@ -20,10 +22,12 @@ import {CurrencyFormatter} from '@services/formatters/currency/currency-formatte
class VarcharFilterFieldTestHostComponent { class VarcharFilterFieldTestHostComponent {
field: Field = { field: Field = {
type: 'varchar', type: 'varchar',
value: 'test filter value',
criteria: { criteria: {
values: ['test filter value'], values: ['test filter value'],
operator: '=' operator: '='
} },
formControl: new FormControl('test filter value')
}; };
} }
@ -38,7 +42,8 @@ describe('VarcharFilterFieldComponent', () => {
VarcharFilterFieldComponent, VarcharFilterFieldComponent,
], ],
imports: [ imports: [
FormsModule VarcharFilterFieldModule,
BrowserAnimationsModule
], ],
providers: [ providers: [
{provide: UserPreferenceStore, useValue: userPreferenceStoreMock}, {provide: UserPreferenceStore, useValue: userPreferenceStoreMock},
@ -70,7 +75,7 @@ describe('VarcharFilterFieldComponent', () => {
it('should have update input when field changes', async(() => { it('should have update input when field changes', async(() => {
expect(testHostComponent).toBeTruthy(); expect(testHostComponent).toBeTruthy();
testHostComponent.field.criteria.values = ['New Field value']; testHostComponent.field.formControl.setValue('New Field value');
testHostFixture.detectChanges(); testHostFixture.detectChanges();
testHostFixture.whenStable().then(() => { testHostFixture.whenStable().then(() => {

View file

@ -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 {BaseFieldComponent} from '@fields/base/base-field.component';
import {DataTypeFormatter} from '@services/formatters/data-type.formatter.service'; 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', templateUrl: './filter.component.html',
styleUrls: [] styleUrls: []
}) })
export class VarcharFilterFieldComponent extends BaseFieldComponent { export class VarcharFilterFieldComponent extends BaseFieldComponent implements OnInit, OnDestroy {
constructor(protected typeFormatter: DataTypeFormatter) { constructor(protected typeFormatter: DataTypeFormatter) {
super(typeFormatter); super(typeFormatter);
} }
get value(): string { ngOnInit(): void {
let current = ''; let current = '';
if (this.field.criteria.values && this.field.criteria.values.length > 0) { if (this.field.criteria.values && this.field.criteria.values.length > 0) {
current = this.field.criteria.values[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.value = newValue;
this.field.criteria.operator = '='; this.field.criteria.operator = '=';
this.field.criteria.values = [newValue]; this.field.criteria.values = [newValue];

View file

@ -3,7 +3,7 @@ import {CommonModule} from '@angular/common';
import {AppManagerModule} from '@base/app-manager/app-manager.module'; import {AppManagerModule} from '@base/app-manager/app-manager.module';
import {VarcharFilterFieldComponent} from './filter.component'; import {VarcharFilterFieldComponent} from './filter.component';
import {FormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
@NgModule({ @NgModule({
declarations: [VarcharFilterFieldComponent], declarations: [VarcharFilterFieldComponent],
@ -11,7 +11,8 @@ import {FormsModule} from '@angular/forms';
imports: [ imports: [
CommonModule, CommonModule,
AppManagerModule.forChild(VarcharFilterFieldComponent), AppManagerModule.forChild(VarcharFilterFieldComponent),
FormsModule FormsModule,
ReactiveFormsModule
] ]
}) })
export class VarcharFilterFieldModule { export class VarcharFilterFieldModule {

View file

@ -3,6 +3,7 @@ import {BulkActionsMap} from '@app-common/actions/bulk-action.model';
import {LineAction} from '@app-common/actions/line-action.model'; import {LineAction} from '@app-common/actions/line-action.model';
import {ChartTypesMap} from '@app-common/containers/chart/chart.model'; import {ChartTypesMap} from '@app-common/containers/chart/chart.model';
import {WidgetMetadata} from '@app-common/metadata/widget.metadata'; import {WidgetMetadata} from '@app-common/metadata/widget.metadata';
import {FieldDefinition} from '@app-common/record/field.model';
export interface ListViewMeta { export interface ListViewMeta {
fields: ColumnDefinition[]; fields: ColumnDefinition[];
@ -33,6 +34,7 @@ export interface SearchMetaField {
label?: string; label?: string;
default?: boolean; default?: boolean;
options?: string; options?: string;
fieldDefinition?: FieldDefinition;
} }
export interface SearchMeta { export interface SearchMeta {

View file

@ -7,7 +7,7 @@ export interface AttributeMap {
export interface Record { export interface Record {
id?: string; id?: string;
type: string; type?: string;
module: string; module: string;
attributes: AttributeMap; attributes: AttributeMap;
fields?: FieldMap; fields?: FieldMap;

View file

@ -1,5 +1,6 @@
export interface SearchCriteriaFieldFilter { export interface SearchCriteriaFieldFilter {
field?: string; field?: string;
fieldType?: string;
operator: string; operator: string;
values?: string[]; values?: string[];
start?: string; start?: string;

View file

@ -14,6 +14,7 @@ import {LanguageStore} from '@store/language/language.store';
import {languageStoreMock} from '@store/language/language.store.spec.mock'; import {languageStoreMock} from '@store/language/language.store.spec.mock';
import {RouterTestingModule} from '@angular/router/testing'; import {RouterTestingModule} from '@angular/router/testing';
import {ApolloTestingModule} from 'apollo-angular/testing'; import {ApolloTestingModule} from 'apollo-angular/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
describe('ListFilterComponent', () => { describe('ListFilterComponent', () => {
let testHostComponent: ListFilterComponent; let testHostComponent: ListFilterComponent;
@ -31,7 +32,8 @@ describe('ListFilterComponent', () => {
DropdownButtonModule, DropdownButtonModule,
FieldGridModule, FieldGridModule,
RouterTestingModule, RouterTestingModule,
ApolloTestingModule ApolloTestingModule,
BrowserAnimationsModule
], ],
providers: [ providers: [
{provide: ListViewStore, useValue: listviewStoreMock}, {provide: ListViewStore, useValue: listviewStoreMock},

View file

@ -5,10 +5,15 @@ import {DropdownButtonInterface} from '@components/dropdown-button/dropdown-butt
import {ButtonInterface} from '@components/button/button.model'; import {ButtonInterface} from '@components/button/button.model';
import {deepClone} from '@base/app-common/utils/object-utils'; import {deepClone} from '@base/app-common/utils/object-utils';
import {combineLatest, Observable} from 'rxjs'; import {combineLatest, Observable} from 'rxjs';
import {map} from 'rxjs/operators'; import {filter, map, startWith, take} from 'rxjs/operators';
import {Field} from '@app-common/record/field.model'; import {Field, FieldMap} from '@app-common/record/field.model';
import {SearchCriteria, SearchCriteriaFieldFilter} from '@app-common/views/list/search-criteria.model'; import {SearchCriteria, SearchCriteriaFieldFilter} from '@app-common/views/list/search-criteria.model';
import {Filter, SearchMetaField} from '@app-common/metadata/list.metadata.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 { export interface FilterDataSource {
getFilter(): Observable<Filter>; getFilter(): Observable<Filter>;
@ -34,8 +39,14 @@ export class ListFilterComponent implements OnInit {
searchCriteria: SearchCriteria; searchCriteria: SearchCriteria;
vm$: Observable<any>; vm$: Observable<any>;
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( this.vm$ = combineLatest([listStore.criteria$, listStore.metadata$]).pipe(
map(([criteria, metadata]) => { map(([criteria, metadata]) => {
@ -51,6 +62,11 @@ export class ListFilterComponent implements OnInit {
this.reset(); this.reset();
this.record = {
module: this.listStore.getModuleName(),
attributes: {}
} as Record;
this.initFields(); this.initFields();
this.initGridButtons(); this.initGridButtons();
this.initHeaderButtons(); this.initHeaderButtons();
@ -72,15 +88,23 @@ export class ListFilterComponent implements OnInit {
} }
const searchFields = searchMeta.layout[type]; const searchFields = searchMeta.layout[type];
const fields = {} as FieldMap;
const formControls = {} as { [key: string]: AbstractControl };
Object.keys(searchFields).forEach(key => { Object.keys(searchFields).forEach(key => {
const name = searchFields[key].name; const name = searchFields[key].name;
fields[name] = this.buildField(searchFields[key], languages, searchCriteria);
formControls[name] = fields[name].formControl;
if (name.includes('_only')) { if (name.includes('_only')) {
this.special.push(this.buildField(searchFields[key], languages, searchCriteria)); this.special.push(fields[name]);
} else { } else {
this.fields.push(this.buildField(searchFields[key], languages, searchCriteria)); this.fields.push(fields[name]);
} }
}); });
this.record.formGroup = new FormGroup(formControls);
} }
protected reset(): void { protected reset(): void {
@ -128,21 +152,34 @@ export class ListFilterComponent implements OnInit {
} }
protected buildField(searchField: SearchMetaField, languages: LanguageStrings, searchCriteria: SearchCriteria): Field { protected buildField(searchField: SearchMetaField, languages: LanguageStrings, searchCriteria: SearchCriteria): Field {
const module = this.listStore.appState.module;
const fieldName = searchField.name; 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 { const definition = {
type: 'varchar',
value: '',
name: searchField.name, name: searchField.name,
label: this.language.getFieldLabel(searchField.label, module, languages), label: searchField.label,
criteria: this.searchCriteria.filters[fieldName] type,
} as Field; 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; let fieldCriteria: SearchCriteriaFieldFilter;
if (searchCriteria.filters[fieldName]) { if (searchCriteria.filters[fieldName]) {
@ -150,8 +187,9 @@ export class ListFilterComponent implements OnInit {
} else { } else {
fieldCriteria = { fieldCriteria = {
field: fieldName, field: fieldName,
fieldType,
operator: '', operator: '',
values: [], values: []
}; };
} }
@ -159,8 +197,28 @@ export class ListFilterComponent implements OnInit {
} }
protected applyFilter(): void { protected applyFilter(): void {
this.listStore.showFilters = false; this.validate().pipe(take(1)).subscribe(valid => {
this.listStore.updateSearchCriteria(this.searchCriteria);
if (valid) {
this.listStore.showFilters = false;
this.listStore.updateSearchCriteria(this.searchCriteria);
return;
}
this.message.addWarningMessageByKey('LBL_VALIDATION_ERRORS');
});
}
protected validate(): Observable<boolean> {
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 { protected clearFilter(): void {

View file

@ -91,7 +91,17 @@ export class DatetimeFormatter implements Formatter {
} }
toUserFormat(dateString: string): string { 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 { toInternalFormat(dateString: string): string {

View file

@ -1,7 +1,7 @@
import {Record} from '@app-common/record/record.model'; import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model'; import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {LanguageStore} from '@store/language/language.store'; 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 {Field, FieldDefinition} from '@app-common/record/field.model';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {ValidationManager} from '@services/record/validation/validation.manager'; 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 { public buildField(record: Record, viewField: ViewFieldDefinition, language: LanguageStore = null): Field {
const definition = (viewField && viewField.fieldDefinition) || {} as FieldDefinition; 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 type = (viewField && viewField.type) || '';
const source = (definition && definition.source) || ''; const source = (definition && definition.source) || '';
const rname = (definition && definition.rname) || 'name'; const rname = (definition && definition.rname) || 'name';
const viewName = viewField.name || ''; const viewName = viewField.name || '';
let value; let value: string;
let valueList = null; let valueList: string[] = null;
if (!viewName) { if (!viewName || !record.attributes[viewName]) {
value = ''; value = '';
} else if (type === 'relate' && source === 'non-db' && rname !== '') { } else if (type === 'relate' && source === 'non-db' && rname !== '') {
value = record.attributes[viewName][rname]; value = record.attributes[viewName][rname];
@ -48,8 +74,40 @@ export class FieldManager {
value = null; value = null;
} }
const validators = this.validationManager.getValidations(record.module, viewField, record); return {value, valueList};
const asyncValidators = this.validationManager.getAsyncValidations(record.module, viewField, record); }
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'}); const formattedValue = this.typeFormatter.toUserFormat(viewField.type, value, {mode: 'edit'});
@ -73,12 +131,6 @@ export class FieldManager {
if (language) { if (language) {
field.label = this.getFieldLabel(viewField.label, record.module, language); field.label = this.getFieldLabel(viewField.label, record.module, language);
} }
return field; return field;
} }
public getFieldLabel(label: string, module: string, language: LanguageStore): string {
const languages = language.getLanguageStrings();
return language.getFieldLabel(label, module, languages);
}
} }

View file

@ -4,7 +4,7 @@ import {Record} from '@app-common/record/record.model';
import {ViewFieldDefinition} from '@app-common/metadata/metadata.model'; import {ViewFieldDefinition} from '@app-common/metadata/metadata.model';
import {AsyncValidatorFn, ValidatorFn} from '@angular/forms'; import {AsyncValidatorFn, ValidatorFn} from '@angular/forms';
import {AsyncValidatorInterface} from '@services/record/validation/aync-validator.Interface'; 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 {RequiredValidator} from '@services/record/validation/validators/required.validator';
import {CurrencyValidator} from '@services/record/validation/validators/currency.validator'; import {CurrencyValidator} from '@services/record/validation/validators/currency.validator';
import {DateValidator} from '@services/record/validation/validators/date.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'; import {RangeValidator} from '@services/record/validation/validators/range.validator';
export interface ValidationManagerInterface { 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ValidationManager implements ValidationManagerInterface { export class ValidationManager implements ValidationManagerInterface {
protected validators: OverridableMap<ValidatorInterface>; protected saveValidators: OverridableMap<ValidatorInterface>;
protected asyncValidators: OverridableMap<AsyncValidatorInterface>; protected asyncSaveValidators: OverridableMap<AsyncValidatorInterface>;
protected filterValidators: OverridableMap<ValidatorInterface>;
constructor( constructor(
protected requiredValidator: RequiredValidator, protected requiredValidator: RequiredValidator,
@ -48,57 +55,66 @@ export class ValidationManager implements ValidationManagerInterface {
protected phoneValidator: PhoneValidator, protected phoneValidator: PhoneValidator,
) { ) {
this.validators = new OverridableMap<ValidatorInterface>(); this.saveValidators = new OverridableMap<ValidatorInterface>();
this.asyncValidators = new OverridableMap<AsyncValidatorInterface>(); this.asyncSaveValidators = new OverridableMap<AsyncValidatorInterface>();
this.filterValidators = new OverridableMap<ValidatorInterface>();
this.validators.addEntry('default', 'required', requiredValidator); this.saveValidators.addEntry('default', 'required', requiredValidator);
this.validators.addEntry('default', 'range', rangeValidator); this.saveValidators.addEntry('default', 'range', rangeValidator);
this.validators.addEntry('default', 'currency', currencyValidator); this.saveValidators.addEntry('default', 'currency', currencyValidator);
this.validators.addEntry('default', 'date', dateValidator); this.saveValidators.addEntry('default', 'date', dateValidator);
this.validators.addEntry('default', 'datetime', datetimeValidator); this.saveValidators.addEntry('default', 'datetime', datetimeValidator);
this.validators.addEntry('default', 'email', emailValidator); this.saveValidators.addEntry('default', 'email', emailValidator);
this.validators.addEntry('default', 'float', floatValidator); this.saveValidators.addEntry('default', 'float', floatValidator);
this.validators.addEntry('default', 'int', intValidator); this.saveValidators.addEntry('default', 'int', intValidator);
this.validators.addEntry('default', 'phone', phoneValidator); 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 { public registerSaveValidator(module: string, key: string, validator: ValidatorInterface): void {
this.validators.addEntry(module, key, validator); this.filterValidators.addEntry(module, key, validator);
} }
public excludeValidator(module: string, key: string): void { public registerFilterValidator(module: string, key: string, validator: ValidatorInterface): void {
this.validators.excludeEntry(module, key); this.saveValidators.addEntry(module, key, validator);
} }
public registerAsyncValidator(module: string, key: string, validator: AsyncValidatorInterface): void { public excludeSaveValidator(module: string, key: string): void {
this.asyncValidators.addEntry(module, key, validator); this.saveValidators.excludeEntry(module, key);
} }
public excludeAsyncValidator(module: string, key: string): void { public excludeFilterValidator(module: string, key: string): void {
this.validators.excludeEntry(module, key); this.filterValidators.excludeEntry(module, key);
} }
public getValidations(module: string, viewField: ViewFieldDefinition, record: Record): ValidatorFn[] { public registerAsyncSaveValidator(module: string, key: string, validator: AsyncValidatorInterface): void {
let validations = []; this.asyncSaveValidators.addEntry(module, key, validator);
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 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 validations = [];
const entries = this.asyncValidators.getGroupEntries(module); const entries = this.asyncSaveValidators.getGroupEntries(module);
Object.keys(entries).forEach(validatorKey => { Object.keys(entries).forEach(validatorKey => {
@ -111,4 +127,19 @@ export class ValidationManager implements ValidationManagerInterface {
return validations; return validations;
} }
protected filterValidations(entries: MapEntry<ValidatorInterface>, 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;
}
} }