-
-
Save Bidthedog/1dc7d10cda1759061c09f44f7b48cbf3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component } from '@angular/core'; | |
import { ActivatedRoute, Params } from '@angular/router'; | |
import { FormGroup } from '@angular/forms'; | |
import { FormServiceBaseService } from './form-service-base.service'; | |
import { HttpServiceBaseService } from '../shared/http-helpers/http-service-base.service'; | |
import { Debug } from '../shared/debug'; | |
import { IEntityBase } from '../model/ientity-base'; | |
@Component({}) | |
export abstract class FormComponentBaseComponent<TDataModel extends IEntityBase, | |
TDataService extends HttpServiceBaseService<TDataModel>, | |
TFormService extends FormServiceBaseService<TDataModel, TDataService>> { | |
/** | |
* Override to set this component's sub FormGroup name (i.e. if you want to access { details: {} }, then this should be 'details' | |
*/ | |
protected abstract readonly formGroupName: string; | |
constructor( | |
public formService: TFormService, | |
protected route: ActivatedRoute | |
) { } | |
/** | |
* Convenience method. Retrieves the component's sub FormGroup name | |
*/ | |
protected getGroupName(): string { | |
return this.formGroupName; | |
} | |
/** | |
* Convenience method. Retrieves the component's sub FormGoup | |
*/ | |
protected getGroup(): FormGroup { | |
return this.formService.getFormGroup(this.formGroupName); | |
} | |
/** | |
* Convenience method. Retrieves the form service. | |
*/ | |
protected getFormService(): TFormService { | |
return this.formService; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { OnInit, Input, Component, ElementRef } from '@angular/core'; | |
import { FormGroup, AbstractControl } from '@angular/forms'; | |
import { FormServiceBaseService } from './form-service-base.service'; | |
@Component({}) | |
export class FormControlBaseComponent implements OnInit { | |
private _group: FormGroup; | |
private type: string; | |
@Input() service: FormServiceBaseService<any, any>; | |
@Input() groupName: string; | |
@Input() name: string; | |
// The below properties are overriden if this.service is specified | |
@Input() friendlyName: string; | |
@Input() helpText: string; | |
@Input() required: boolean; | |
@Input() set group(group: FormGroup) { this._group = group; }; | |
get group() { | |
// Lazy load the group from the service, if not set manually | |
if (!this._group) { | |
if (!this.service || !this.groupName) { | |
// No service resolution available, user must specify service or group binding | |
throw new Error(`'group' property is required by '${this.type}' component '${this.name}'`); | |
} | |
// Resolve properties using service and formGroupName | |
// Override control defaults with config data | |
this._group = this.service.getFormGroup(this.groupName); | |
} | |
return this._group; | |
} | |
constructor( | |
protected element: ElementRef | |
) { } | |
ngOnInit() { | |
this.type = this.element.nativeElement.tagName.toLowerCase(); | |
if (!this.name) { | |
throw new Error(`'name' property is required by '${this.type}' component. Specify manually in the template.`); | |
} | |
this.setupFromConfig(); | |
if (!this.friendlyName) { | |
throw new Error(`'friendlyName' property is required by '${this.type}' component '${this.name}'`); | |
} | |
} | |
protected setupFromConfig() { | |
if (!this.service || !this.groupName) { | |
// Nothing to set up | |
return; | |
} | |
this.helpText = this.service.getFieldProperty(this.groupName, this.name, 'helpText'); | |
this.friendlyName = this.service.getFieldProperty(this.groupName, this.name, 'friendlyName'); | |
// Set up generic validator aesthetics / HTML5 properties | |
const validators = this.service.getFieldProperty(this.groupName, this.name, 'validators'); | |
if (validators) { | |
this.required = validators.required; | |
} | |
this.setupValidators(validators); | |
} | |
/** | |
* Override this method to setup custom validators for a control component. 'Required' is set up for all components. | |
* @param validators | |
*/ | |
protected setupValidators(validators: any) { }; | |
protected get control(): AbstractControl { | |
return this.group.get(this.name); | |
} | |
protected hasError(): boolean { | |
return !this.control.valid && this.edited(); | |
} | |
protected hasSuccess(): boolean { | |
return this.control.valid && this.edited(); | |
} | |
protected edited(): boolean { | |
return this.control.dirty; | |
} | |
/** | |
* Retrieves the value of the 'data' property for the specified validatorName configured for this control | |
* @param propertyName | |
*/ | |
protected getValidatorData(validatorName: string): any { | |
const validators = this.service.getFieldProperty(this.groupName, this.name, 'validators'); | |
if (!validators) { | |
return null; | |
} | |
if (validators[validatorName]) { | |
const data = validators[validatorName].data; | |
if (!data) { | |
throw new Error(`Unable to find 'data' property for '${validatorName}' validator required by '${this.type}' component '${this.name}'. Data property must contain the setter value.`); | |
} | |
return data; | |
} | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Validators } from '@angular/forms'; | |
import { Constants } from '../../../app.constants'; | |
import { | |
FormHelpers, | |
FormGroupDefinition as gDef, | |
FormFieldDefinition as fDef, | |
FormFieldValidator as val | |
} from '../../../shared-forms/form-helpers/form-helpers'; | |
export class PatientFormDefinition { | |
public details = new gDef('General', 'details', 10, { | |
NHSNumber: new fDef(null, 'NHS Number', 'NHSNumber', FormHelpers.getTextValue, 'Must be in the format \'NNN-NNN-NNNN\'', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required'), | |
pattern: new val(Validators.pattern, '\'{{friendlyName}}\' must be in the format \'NNN-NNN-NNNN\' where N is a digit', Constants.NHSNUMBER_PATTERN) | |
}), | |
TitleId: new fDef(null, 'Title', 'TitleId', FormHelpers.getTextValue, 'Please select the patient\'s title', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
FirstName: new fDef(null, 'First Name', 'FirstName', FormHelpers.getTextValue, 'Please enter the patient\'s first name', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
MiddleNames: new fDef(null, 'Middle Name(s)', 'MiddleNames', FormHelpers.getTextValue, 'If the patient has one or more middle names, please enter them here'), | |
LastName: new fDef(null, 'Last Name', 'LastName', FormHelpers.getTextValue, 'Please enter the patient\'s last name', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
DOB: new fDef(null, 'Date of Birth', 'DOB', FormHelpers.getDateValue, 'Please enter or select the patient\'s Date of Birth', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
GenderId: new fDef(null, 'Gender', 'GenderId', FormHelpers.getLookupValue, 'Please select the patient\'s gender', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
RegionId: new fDef(null, 'Region', 'RegionId', FormHelpers.getLookupValue, 'Please select the geographical region where the patient is being treated - this will be used for determining medical data such as GP and Hospital, and for social grouping and scheduling', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
MaritalStatusId: new fDef(null, 'Marital Status', 'MaritalStatusId', FormHelpers.getLookupValue, 'Please select the patient\'s marital status', { | |
required: new val(Validators.required, '\'{{friendlyName}}\' is required') | |
}), | |
OccupationTypeId: new fDef(null, 'Occupation Status', 'OccupationTypeId', FormHelpers.getLookupValue, 'Please select the closest match for the patient\'s occupation status'), | |
OccupationId: new fDef(null, 'Occupation', 'OccupationId', FormHelpers.getLookupValue, 'Please select the closest match for the patient\'s occupation'), | |
LivesWith: new fDef(null, 'Lives With', 'LivesWith', FormHelpers.getTextValue, 'Please detail who the patient lives with currently'), | |
NextOfKinId: new fDef(null, 'Next of Kin', 'NextOfKinId', FormHelpers.getLookupValue, 'Please add a patient\'s next of kin'), | |
VehicleLicenseTypeId: new fDef(null, 'Vehicle Licenses', 'VehicleLicenseTypeId', FormHelpers.getMultiLookupValue, 'Please select the types of vehicle license that the patient holds', null, true) | |
}); | |
public contact = new gDef('Contact', 'contact', 20, { | |
TelephoneNo: new fDef(null, 'Telephone No.', 'TelephoneNo', FormHelpers.getTextValue, 'Please enter the patient\'s telephone number'), | |
MobileNo: new fDef(null, 'Mobile No.', 'MobileNo', FormHelpers.getTextValue, 'Please enter the patient\'s mobile number'), | |
HouseNumber: new fDef(null, 'House Name / Number', 'HouseNumber', FormHelpers.getTextValue, 'Please enter the patient\'s house name or number'), | |
StreetName: new fDef(null, 'Street Name', 'StreetName', FormHelpers.getTextValue, 'Please enter the patient\'s street name'), | |
Town: new fDef(null, 'Town', 'Town', FormHelpers.getTextValue, 'Please enter the patient\'s town'), | |
City: new fDef(null, 'City', 'City', FormHelpers.getTextValue, 'Please enter the patient\'s city'), | |
County: new fDef(null, 'County', 'County', FormHelpers.getTextValue, 'Please enter the patient\'s county'), | |
CountryId: new fDef(null, 'Country', 'CountryId', FormHelpers.getLookupValue, 'Please select the patient\'s county'), | |
Postcode: new fDef(null, 'Postcode', 'Postcode', FormHelpers.getTextValue, 'Please enter the patient\'s postcode', { | |
pattern: new val(Validators.pattern, '\'{{friendlyName}}\' must be a valid UK postcode', Constants.POSTCODE_PATTERN), | |
maxLength: new val(Validators.maxLength, '\'{{friendlyName}}\' should be no longer than {{data}} characters', 8) | |
}), | |
EmailAddress: new fDef(null, 'Email Address', 'EmailAddress', FormHelpers.getTextValue, 'Please enter the patient\'s email address', { | |
pattern: new val(Validators.pattern, '\'{{friendlyName}}\' must be a valid email address', Constants.EMAIL_PATTERN), | |
maxLength: new val(Validators.maxLength, '\'{{friendlyName}}\' should be no longer than {{data}} characters', 300) | |
}), | |
}); | |
public medical = new gDef('Medical', 'medical', 30, { | |
GPId: new fDef(null, 'GP', 'GPId', FormHelpers.getLookupValue, 'Please select a GP - a region must be selected first', { | |
required: { validator: Validators.required, message: '\'{{friendlyName}}\' is required' } | |
}), | |
HospitalId: new fDef(null, 'Hospital', 'admission.HospitalId', FormHelpers.getLookupValue, 'Please select a hospital - a region must be selected first. This field ', { | |
required: { validator: Validators.required, message: '\'{{friendlyName}}\' is required' } | |
}, false, false), | |
HospitalNumber: new fDef(null, 'Patient Hospital Number', 'admission.HospitalNumber', FormHelpers.getTextValue, 'Please enter the patient\'s hospital number', { | |
required: { validator: Validators.required, message: '\'{{friendlyName}}\' is required' } | |
}), | |
WardId: new fDef(null, 'Ward', 'admission.WardId', FormHelpers.getLookupValue, 'Please select a ward', { | |
required: { validator: Validators.required, message: '\'{{friendlyName}}\' is required' } | |
}), | |
ConsultantId: new fDef(null, 'Consultant', 'admission.ConsultantId', FormHelpers.getLookupValue, 'Please select a consultant', { | |
required: { validator: Validators.required, message: '\'{{friendlyName}}\' is required' } | |
}) | |
}); | |
public conditions = new gDef('Medical Conditions', 'conditions', 40, { | |
MedicalConditionId: new fDef(null, 'Medical Conditions', 'admission.MedicalConditionId', FormHelpers.getMultiLookupValue, 'Please select the medical conditions that the patient is being treated for during this admission', null, true), | |
MedicalConditionClassId: new fDef(null, 'Medical Conditions', 'admission.MedicalConditionClassId', FormHelpers.getMultiLookupValue, 'Please specify the classifications for the selected medical conditions', null, true), | |
}); | |
public risk = new gDef('Risk Factors', 'risk', 50, null); | |
public diagnosis = new gDef('Diagnosis', 'diagnosis', 60, null); | |
public treatment = new gDef('Treatment', 'treatment', 70, null); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FormGroup, FormControl, FormArray } from '@angular/forms'; | |
import { Debug } from '../../shared/debug'; | |
export class FormGroupDefinition { | |
constructor( | |
public friendlyName: string, | |
public urlSegment: string, | |
public order: number, | |
public fields: any | |
) { } | |
} | |
export class FormFieldDefinition<T> { | |
constructor( | |
public defaultValue: T, | |
public friendlyName: string, | |
public modelPath: string, | |
public mappingFunc: (val: T, defaultVal: T) => T, | |
public helpText: string = null, | |
public validators: any = null, | |
public multiValue: boolean = false, | |
public enabled: boolean = true | |
) { } | |
} | |
export class FormGroupErrors { | |
constructor( | |
public group?: string, | |
public errors?: string[] | |
) { | |
this.errors = [] as string[]; | |
} | |
} | |
export class FormFieldValidator { | |
constructor( | |
public validator: any, | |
public message: string, | |
public data: any = null | |
) { } | |
} | |
export class FormHelpers { | |
/** | |
* Flattens the passed formValues into the model provided, using the formConfig's modelPath property | |
* @param model The model to update | |
* @param formValues The form to use as the data source | |
* @param formConfig The form config object to use for mapping | |
*/ | |
public static buildDataModel<TModel>(model: TModel, formValues: FormGroup, formConfig: any): TModel { | |
if (!model) { | |
throw new Error(`Unable to build data model - model must be instantiated by the caller`); | |
} | |
for (const groupName in formConfig) { | |
if (!formConfig.hasOwnProperty(groupName)) { | |
continue; | |
} | |
const cfgGroup = this.getConfigGroup(formConfig, groupName); | |
const valueGroup = formValues.get(groupName) as FormGroup; // Form group | |
for (const fieldName in cfgGroup.fields) { | |
if (!cfgGroup.hasOwnProperty(fieldName)) { | |
continue; | |
} | |
const valueField = valueGroup.get(fieldName); // Form value | |
const modelPath = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'modelPath'); | |
model[modelPath] = valueField.value; | |
} | |
} | |
return model; | |
} | |
/** | |
* Marks all invalid controls as dirty so they can be visually validated | |
* @param formRoot | |
*/ | |
public static markInvalidAsDirty(formRoot: FormGroup) { | |
for (const groupName in formRoot.controls) { | |
if (!formRoot.controls.hasOwnProperty(groupName)) { | |
continue; | |
} | |
const group = formRoot.get(groupName) as FormGroup; | |
for (const fieldName in group.controls) { | |
if (!group.controls.hasOwnProperty(fieldName)) { | |
continue; | |
} | |
const field = group.get(fieldName); | |
if (!field.valid) { | |
field.markAsDirty(); | |
} | |
} | |
} | |
} | |
/** | |
* Builds a FormGroup from the formConfig passed in. Generally used to create a form model from scrach on initialisation | |
* @param formConfig | |
*/ | |
public static buildFormGroup(formConfig: any) { | |
const groups: any = {}; | |
for (const groupName in formConfig) { | |
if (!formConfig.hasOwnProperty(groupName)) { | |
Debug.warn(`Cannot find group '${groupName}' in form config:\r\n${JSON.stringify(formConfig)}`); | |
continue; | |
} | |
const cfgGroup = this.getConfigGroup(formConfig, groupName).fields; | |
const fields: any = {}; | |
for (const fieldName in cfgGroup) { | |
if (!cfgGroup.hasOwnProperty(fieldName)) { | |
Debug.warn(`Cannot find field '${fieldName}' in group '${groupName}' in form config:\r\n${JSON.stringify(formConfig, null, ' ')}`); | |
continue; | |
} | |
const cfgDefaultValue = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'defaultValue'); | |
const cfgValidators = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'validators'); | |
const cfgMultiValue = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'multiValue'); | |
let validators: any = null; | |
if (cfgValidators) { | |
validators = this.buildValidators(cfgValidators); | |
} | |
// Create the field | |
fields[fieldName] = new FormControl(cfgDefaultValue, validators); | |
} | |
groups[groupName] = new FormGroup(fields); | |
} | |
return new FormGroup(groups); | |
} | |
/** | |
* Builds an array of validators with validator functions and data applied from the form configuration | |
* @param cfgValidators | |
*/ | |
private static buildValidators(cfgValidators: any) { | |
const validators: any = []; | |
for (const validatorName in cfgValidators) { | |
if (!cfgValidators.hasOwnProperty(validatorName)) { | |
continue; | |
} | |
const vFunc = cfgValidators[validatorName].validator; | |
const vFuncData = cfgValidators[validatorName].data; | |
// Create the function with the specified data passed in, if the data field exists | |
const validatorFunction = vFuncData ? vFunc(vFuncData) : vFunc; | |
validators.push(validatorFunction); | |
} | |
return validators; | |
} | |
/** | |
* Builds an object from the formConfig and populates it with the specified model values, running each field's mapping function. Generally used with formModel.setValue(), formModel.patchValue() or formModel.reset() | |
* @param formConfig | |
* @param model | |
*/ | |
public static buildFormValues(formConfig: any, model: any) { | |
const groups: any = {}; | |
for (const groupName in formConfig) { | |
if (!formConfig.hasOwnProperty(groupName)) { | |
continue; | |
} | |
const cfgGroup = this.getConfigGroup(formConfig, groupName).fields; | |
const fields: any = {}; | |
for (const fieldName in cfgGroup) { | |
if (!cfgGroup.hasOwnProperty(fieldName)) { | |
continue; | |
} | |
const modelPath = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'modelPath'); | |
const mappingFunc = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'mappingFunc'); | |
const modelValue = model[modelPath]; | |
fields[fieldName] = mappingFunc(modelValue); | |
} | |
groups[groupName] = fields; | |
} | |
return groups; | |
} | |
/** | |
* Retrieves a list of errors from the specified formRoot, and searches the formConfig for error messages | |
* @param formConfig | |
* @param formRoot | |
*/ | |
public static getFormErrorList(formConfig: any, formRoot: FormGroup): FormGroupErrors[] { | |
const errors: FormGroupErrors[] = []; | |
// Search the form for errors | |
for (const groupName in formRoot.controls) { | |
if (!formRoot.controls.hasOwnProperty(groupName)) { | |
continue; | |
} | |
const group = formRoot.get(groupName) as FormGroup; | |
const friendlyGroupName: string = this.getConfigGroup(formConfig, groupName).friendlyName; | |
const errorGroup = new FormGroupErrors(friendlyGroupName); | |
for (const fieldName in group.controls) { | |
if (!group.controls.hasOwnProperty(fieldName)) { | |
continue; | |
} | |
const field = group.get(fieldName); | |
const friendlyName = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'friendlyName'); | |
for (const errorType in field.errors) { | |
if (!field.errors.hasOwnProperty(errorType)) { | |
continue; | |
} | |
// Retrieve error message from config | |
const cfgValidators = this.getConfigFieldProperty(formConfig, groupName, fieldName, 'validators'); | |
if (!cfgValidators) { | |
Debug.warn(`Unable to find \'validators\' property in form config for \'${fieldName}\' field (case sensitive)`); | |
continue; | |
} | |
const validator = cfgValidators[errorType]; | |
if (!validator) { | |
Debug.warn(`Unable to find \'${errorType}\' validator in form config for \'${fieldName}\' field (case sensitive)`); | |
continue; | |
} | |
// Do replacements in error message (DRY!) | |
let formattedMsg = validator.message.replace('{{data}}', validator.data); | |
formattedMsg = formattedMsg.replace('{{friendlyName}}', friendlyName); | |
errorGroup.errors.push(formattedMsg); | |
} | |
} | |
if (errorGroup.errors && errorGroup.errors.length > 0) { | |
errors.push(errorGroup); | |
} | |
} | |
return errors; | |
} | |
/*++Mapping Methods */ | |
public static getTextValue(val: string, defaultValue: string = null): string { | |
return val || defaultValue; | |
} | |
public static getDateValue(val: Date, defaultValue: Date = null): Date { | |
return val || defaultValue; | |
} | |
public static getLookupValue(val: number): number { | |
if (val === undefined || val == null || val <= 0) { | |
return null; | |
} | |
return val; | |
} | |
public static getMultiLookupValue(val: number[]): number[] { | |
if (val === undefined || val == null) { | |
return null; | |
} | |
return val; | |
} | |
/*--Mapping Methods */ | |
/*++Config Methods */ | |
public static getConfigGroup(formConfig: any, groupName: string): FormGroupDefinition { | |
const cfgGroup = formConfig[groupName]; | |
return cfgGroup; | |
} | |
public static getConfigGroups(formConfig: any): FormGroupDefinition[] { | |
const cfgGroups: FormGroupDefinition[] = []; | |
for (const groupName in formConfig) { | |
if (!formConfig.hasOwnProperty(groupName)) { | |
continue; | |
} | |
cfgGroups.push(this.getConfigGroup(formConfig, groupName)); | |
} | |
return cfgGroups; | |
} | |
public static getConfigField(formConfig: any, groupName: string, fieldName: string): any { | |
const cfgGroup = this.getConfigGroup(formConfig, groupName); | |
if (!cfgGroup) { | |
return null; | |
} | |
const cfgField = cfgGroup.fields[fieldName]; | |
if (!cfgField) { | |
return null; | |
} | |
return cfgField; | |
} | |
public static getConfigFieldProperty(formConfig: any, groupName: string, fieldName: string, propertyName: string) { | |
const cfgField = this.getConfigField(formConfig, groupName, fieldName); | |
if (!cfgField) { | |
Debug.warn(`Could not find field '${groupName}.fields.${fieldName}' in form config`); | |
return null; | |
} | |
const cfgProperty = cfgField[propertyName]; | |
if (!cfgProperty) { | |
return null; | |
} | |
return cfgProperty; | |
} | |
/*--Config Methods */ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { FormGroup } from '@angular/forms'; | |
import { Observable } from 'rxjs/Observable'; | |
import { Subscription } from 'rxjs/Subscription'; | |
import 'rxjs/Rx'; | |
import { IEntityBase } from '../model/ientity-base'; | |
import { FormHelpers, FormGroupErrors } from '../shared-forms/form-helpers/form-helpers'; | |
import { HttpServiceBaseService, HTTPMappedResponse } from '../shared/http-helpers/http-service-base.service'; | |
import { Debug } from '../shared/debug'; | |
@Injectable() | |
export abstract class FormServiceBaseService<TDataModel extends IEntityBase, TDataService extends HttpServiceBaseService<TDataModel>>{ | |
// ++ Private / Protected fields | |
private _formModel: FormGroup; | |
private _dataLoaded: boolean = false; | |
protected _dataLoadError: boolean = false; | |
private _formPopulated: boolean = false; | |
private _formConfig: any; | |
protected _dataModel: TDataModel; | |
private _dataModelObserver: Observable<TDataModel> = Observable.create(o => { | |
o.next(this.createDataModel()); | |
o.complete(); | |
}); | |
protected _submitAttempt: boolean = false; | |
protected _submitSuccess: boolean = false; | |
// -- Private fields | |
// ++ Public getters | |
public get formModel(): FormGroup { return this._formModel; }; | |
public get dataLoaded(): boolean { return this._dataLoaded; }; | |
public get dataLoadError(): boolean { return this._dataLoadError; }; | |
public get dataModel(): TDataModel { return this._dataModel; }; | |
public get formConfig(): any { return this._formConfig; }; | |
public get formPopulated(): boolean { return this._formPopulated; }; | |
public get submitAttempt(): boolean { return this._submitAttempt; }; | |
public get submitSuccess(): boolean { return this._submitSuccess; }; | |
public get dataModelObserver(): Observable<TDataModel> { return this._dataModelObserver; } | |
// -- Public getters | |
constructor( | |
protected httpDataService: HttpServiceBaseService<TDataModel> | |
) { | |
// Set config from overriden method | |
this._formConfig = this.createConfig(); | |
// Create the form structure immediately | |
this.createForm(); | |
} | |
// ++ Abstract members | |
/** | |
* Implement onSubmit() to deal with the form submission. Only called once the form is complete and valid. | |
*/ | |
public onSubmit(): Observable<TDataModel> { | |
this._submitAttempt = true; // setting true will enable the error summary | |
if (!this.isValid()) { | |
// If the form is not valid, mark all invalid fields as dirty | |
// so they can be visually validated | |
FormHelpers.markInvalidAsDirty(this.formModel); | |
return Observable.empty(); | |
} | |
// Convert form to model | |
this._dataModel = FormHelpers.buildDataModel(this.createDataModel(), this.formModel, this.formConfig); | |
// Save model | |
let observable: Observable<TDataModel>; | |
if (this.dataModel.Id > 0) { | |
// Put existing | |
observable = this | |
.httpDataService | |
.put(this.dataModel) | |
.map(x => x.data); | |
} else { | |
// Post new | |
observable = this | |
.httpDataService | |
.post(this.dataModel) | |
.map(x => x.data); | |
} | |
observable | |
.subscribe(dataModel => { | |
this._submitSuccess = true; | |
}, error => { | |
this._submitSuccess = false; | |
}); | |
return observable; | |
} | |
/** | |
* Implement createConfig() to return the service's form configuration object. | |
*/ | |
protected abstract createConfig(): any; | |
/** | |
* Retrieves a model then populates the form, returning an observable that can be subscribed to | |
*/ | |
public subscribeToModel(id: number): Observable<TDataModel> { | |
// Perform a full service reset so loaders etc show | |
this.resetForm(); | |
if (id && id > 0) { | |
// Get data from service | |
this._dataModelObserver = this.httpDataService.get(id, null).map(x => x.data); | |
} else { | |
// Create new model and assign to new observable | |
this._dataModelObserver = Observable.create(o => { | |
o.next(this.createDataModel()); | |
o.complete(); | |
}); | |
} | |
// Subscribe to populate the form on success | |
this._dataModelObserver | |
.subscribe(model => this.populateForm(model), e => null); | |
return this._dataModelObserver; | |
} | |
/** | |
* Override to provide a generics helper - simply new up a TDataModel | |
*/ | |
protected abstract createDataModel(): TDataModel; | |
// -- Abstract members | |
// ++ Public members | |
public getErrorHTML(): string { | |
let errorMessage: string = '<p>At least one form error occurred during validation:</p>'; | |
const errors = this.getFormErrorList(); | |
for (const errorGroup of errors) { | |
errorMessage += `<p>${errorGroup.group}</p><ul>`; | |
for (const error of errorGroup.errors) { | |
errorMessage += '<li>' + error + '</li>'; | |
} | |
errorMessage += '</ul>'; | |
} | |
return errorMessage; | |
} | |
/** | |
* Returns a list of FormError objects, which contains a list of friendly group names, | |
* each with a list of human readible errors that have occurred | |
*/ | |
public getFormErrorList(): FormGroupErrors[] { | |
return FormHelpers.getFormErrorList(this.formConfig, this.formModel); | |
} | |
/** | |
* Returns true if the form has been populated, is not valid, and has been edited (dirty) | |
*/ | |
public showErrors(): boolean { | |
return this.formPopulated && !this.formModel.valid && this.formModel.dirty; | |
} | |
/** | |
* Returns true if the form has been edited (dirty) | |
*/ | |
public canClickSubmit(): boolean { | |
return this.formModel.dirty; | |
} | |
/** | |
* Returns true if the form is valid. | |
*/ | |
public isValid(): boolean { | |
return this.formModel.valid; | |
} | |
public getFieldProperty(groupName: string, fieldName: string, propertyName: string): any { | |
return FormHelpers.getConfigFieldProperty(this.formConfig, groupName, fieldName, propertyName); | |
} | |
public getFormGroup(groupName: string): FormGroup { | |
return this.formModel.get(groupName) as FormGroup; | |
} | |
// -- Public members | |
// ++ Helpers | |
private resetForm() { | |
this._dataLoaded = false; | |
this._dataLoadError = false; | |
this._formPopulated = false; | |
this._dataModel = null; | |
this._submitAttempt = false; | |
this._submitSuccess = false; | |
} | |
/** | |
* Called from within the base class constructor. Creates the form and sets the FormModel field. | |
*/ | |
private createForm(): void { | |
this._formModel = FormHelpers.buildFormGroup(this.formConfig); | |
} | |
/** | |
* Call once from within getModel to populate the form once data has been loaded | |
*/ | |
protected populateForm(model: TDataModel): void { | |
// Set the model | |
this._dataModel = model; | |
if (model) { | |
// Populate the form values | |
const values = FormHelpers.buildFormValues(this.formConfig, model); | |
this.formModel.reset(values); | |
this._dataLoaded = true; | |
} else { | |
Debug.warn('dataModel not set for ' + this.constructor.name + '. Unable to populate form model (case sensitive).'); | |
} | |
this._formPopulated = true; | |
} | |
// -- Helpers | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment