mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 11:00:40 +08:00
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:
parent
0916d14a5c
commit
61de0618ea
15 changed files with 270 additions and 93 deletions
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue