/* eslint-disable no-param-reassign */ import { IEvent, IField, IFieldEnum, IFieldEnumParams, IFieldGroup, IFieldGroupParams, IFieldList, IFieldListParams, IFieldMap, IFieldParams, IFieldStatus, IForm, IFormParams, IFormPurified, IIgnoreValueType, IListenerManager, IRuleTrigger, ISchema } from '@wowpic/xform-types'; import EventEmitter from 'events'; import Field from './field'; import FieldGroup from './fieldGroup'; import FieldList from './fieldList'; import ListenerManager from './listenerManager'; import { clone, isNull, isUndefined } from './utils'; export default class Form implements IForm { public key: string; public schema: ISchema; public fields: IFieldEnum[]; public fieldMap: IFieldMap; public ignoreValues: IIgnoreValueType[]; public initialValues: { [key: string]: any }; public initialStatus: IFieldStatus; static components: any[] = []; static componentMap: { [name: string]: any } = {}; public listenerManager: IListenerManager; private emitter: EventEmitter; private timerMap: { [event: string]: any }; constructor(params: IFormParams = {}) { this.initialize(params); } public initialize(params: IFormParams) { this.schema = params.schema || []; this.ignoreValues = params.ignoreValues || []; this.initialValues = params.values ? clone(params.values) : null; this.initialStatus = params.status || null; this.key = params.key || ''; this.fields = []; this.fieldMap = {}; this.emitter = new EventEmitter(); this.emitter.setMaxListeners(1000); this.timerMap = {}; this.listenerManager = new ListenerManager(this); this.schema.forEach(fieldParams => this.registerField(fieldParams)); this.finishRegisterField(); } private addFieldListener() { this.fields.forEach(field => { this.listenerManager.addField(field); if (field.type !== 'Array' && field.type !== 'Object') { this.listenerManager.addOptionManager((field).optionManager); } }); } private triggerFieldListener() { this.fields.forEach(field => this.listenerManager.trigger(field, 'field')); } public finishRegisterField() { this.addFieldListener(); this.triggerFieldListener(); } public registerField(params: IFieldEnumParams) { const fieldParams = { ...params }; const key = fieldParams.key; if (!isNull(this.initialValues) && !isUndefined(this.initialValues[key])) { fieldParams.value = clone(this.initialValues[key]); } if (!isNull(this.initialStatus)) { fieldParams.status = this.initialStatus; } if (fieldParams.type === 'Array') { const fieldList: IFieldList = new FieldList(this, fieldParams); fieldList.on('BLUR', () => { this.emit('BLUR'); }); this.fields.push(fieldList); this.fieldMap[key] = fieldList; return fieldList; } if (fieldParams.type === 'Object') { const fieldGroup: IFieldGroup = new FieldGroup(this, fieldParams); fieldGroup.on('BLUR', () => { this.emit('BLUR'); }); this.fields.push(fieldGroup); this.fieldMap[key] = fieldGroup; return fieldGroup; } const field: IField = new Field(this, fieldParams); field.on('BLUR', () => { this.emit('BLUR'); }); this.fields.push(field); this.fieldMap[key] = field; return field; } static registerComponent(name: string, component: any) { this.components.push(component); this.componentMap[name] = component; return component; } static getComponent(name: string) { return this.componentMap[name] || null; } public registerComponent(name: string, component: any) { Form.components.push(component); Form.componentMap[name] = component; return component; } public getComponent(name: string) { return Form.componentMap[name] || null; } public async submit(trigger?: IRuleTrigger) { if (trigger) { await Promise.all(this.fields.map(field => field.validate(trigger))); } else { await Promise.all(this.fields.map((field: IFieldEnum) => { return (async () => { const blurResult = await field.validate('blur'); if (blurResult.valid) { const changeResult = await field.validate('change'); if (changeResult.valid) { await field.validate('submit'); } } })(); })); } const valid: boolean = await this.validate(); const value: any = this.getValue(); this.emit('SUBMIT'); return { valid, value }; } public async reset() { await Promise.all(this.fields.map(field => field.reset())); this.triggerFieldListener(); this.emit('RESET'); } public async empty() { await Promise.all(this.fields.map(field => field.empty())); this.triggerFieldListener(); this.emit('EMPTY'); } public async validate() { const filterFields = this.fields .filter(field => field.status !== 'hidden' && field.status !== 'preview'); const fieldValid = filterFields .every(field => { return field.valid; }); const arrayOrGroupValid = (await Promise.all( filterFields .filter(field => field.type === 'Array' || field.type === 'Object') .map(field => (field).childrenValidate())) ).every(vaild => vaild); return fieldValid && arrayOrGroupValid; } public getValue(key?: string) { if (key) { const field = this.fieldMap[key]; return field ? field.getValue() : null; } const { ignoreValues } = this; return this.fields.filter(field => { if (ignoreValues.includes('hidden') && field.status === 'hidden') { return false; } if (ignoreValues.includes('preview') && field.status === 'preview') { return false; } if (ignoreValues.includes('disabled') && field.status === 'disabled') { return false; } if (ignoreValues.includes('null') && isNull(field.value)) { return false; } if (ignoreValues.includes('undefined') && isUndefined(field.value)) { return false; } if (ignoreValues.includes('falseLike') && !field.value) { return false; } return true; }).reduce((result: any, field) => { result[field.key] = field.value; return result; }, {}); } public async setValue(values: { [key: string]: any }) { await Promise.all(this.fields.reduce((result: any, field) => { const key = field.key; const value = values?.[key]; if (!isUndefined(value)) { result.push(field.setValue(value)); } return result; }, [])); } public setStatus(status: IFieldStatus) { this.fields.forEach(field => field.setStatus(status)); } public purify() { const formPurified: IFormPurified = {}; this.fields.forEach(field => { const key = field.key; const fieldPurified = field.purify(); formPurified[key] = fieldPurified; }); return formPurified; } public on(event: IEvent, listener: (...args: any[]) => void) { this.emitter.on(event, listener); } public emit(event: IEvent, ...args: any[]) { const timer = this.timerMap[event]; clearTimeout(timer); this.timerMap[event] = setTimeout(() => { this.emitter.emit(event, ...args); }); } public removeListener(event: IEvent, listener: (...args: any[]) => void) { this.emitter.removeListener(event, listener); } }