/* eslint-disable @typescript-eslint/no-this-alias */ /* eslint-disable no-param-reassign */ import { IEvent, IFieldEnum, IFieldGroup, IFieldGroupParams, IFieldList, IFieldListParams, IFieldParams, IFieldProps, IFieldStatus, IFieldUI, IForm, IListener, IListenerSet, IRule, IRuleStatus, IRuleTrigger, ISchema, IValidateResult, IValidator } from '@wowpic/xform-types'; import EventEmitter from 'events'; import Field from './field'; import FieldList from './fieldList'; import { clone, computeRules2UI, computeUI2Rules, filterByKeys, isEqual, isNull, isUndefined } from './utils'; import Validator from './validator'; export default class FieldGroup { public key: string; public type: string; public status: IFieldStatus; public ui: IFieldUI; public props: IFieldProps; public initialStatus: IFieldStatus; public initialUI: IFieldUI; public initialProps: IFieldProps; public rules: IRule[]; public listeners: IListener[]; public children: IFieldEnum[]; public valid: boolean; public message: string; public validStatus: IRuleStatus; public context: IForm; private validator: IValidator; private emitter: EventEmitter; private timerMap: { [event: string]: any }; public get value() { return this.getValue(); } public initialValue: { [key: string]: any }; constructor(context: IForm, params: IFieldGroupParams) { this.context = context; this.initialize(params); } public async initialize(params: IFieldGroupParams) { this.key = params.key; this.type = params.type || 'Object'; this.status = params.status || 'edit'; this.ui = computeRules2UI({ rules: params.rules, ui: params.ui }) || {}; this.props = clone(params.props) || {}; this.initialStatus = params.status || 'edit'; this.initialUI = clone(params.ui) || {}; this.initialProps = clone(params.props) || {}; this.rules = computeUI2Rules({ rules: params.rules, ui: params.ui }) || []; this.listeners = clone(params.listeners) || []; this.validStatus = 'normal'; this.valid = true; this.message = ''; this.emitter = new EventEmitter(); this.emitter.setMaxListeners(1000); this.timerMap = {}; this.validator = new Validator(this, { rules: this.rules }); this.children = this.getChildren(params.children); params.value && this.setValue(params.value); this.initialValue = clone(this.value); await this.validate('change'); } private getChildren(schema: ISchema = []) { return schema.map(fieldParams => { const params = { ...fieldParams, status: fieldParams.status || this.status }; const type = params.type; if (type === 'Array') { return new FieldList(this.context, params); } if (type === 'Object') { return new FieldGroup(this.context, params); } return new Field(this.context, params); }).map(field => { field.on('VALUE_CHANGE', () => { this.validate('change'); this.triggerEvent('VALUE_CHANGE'); }); field.on('BLUR', () => { this.validate('blur'); this.emit('BLUR'); }); return field; }); } public purify() { return this.children.reduce((result: any, field) => { const key = field.key; result[key] = field.purify(); return result; }, {}); } public async validate(trigger: IRuleTrigger = 'submit') { if (trigger === 'submit') { await Promise.all(this.children.map(field => field.validate(trigger))); } const { valid, message, status } = await this.validator.validate(trigger); if (this.valid !== valid || this.message !== message || this.validStatus !== status) { this.triggerEvent('VALID_CHANGE'); } this.valid = valid; this.message = message; this.validStatus = status; this.emit('VALIDATE', trigger, this.value); return { valid, message, status }; } public async childrenValidate() { const fieldValid = this.children .every(field => { return field.valid; }); const arrayOrGroupValid = (await Promise.all( this.children .filter(field => field.type === 'Array' || field.type === 'Object') .map(field => (field).childrenValidate())) ).every(vaild => vaild); return fieldValid && arrayOrGroupValid; } public async set(state: IListenerSet) { if (state.status) { this.setStatus(state.status); } if (state.ui) { this.setUI(state.ui); } if (state.props) { this.setProps(state.props); } if (!isUndefined(state.value)) { await this.setValue(state.value); } } public async unset(state: IListenerSet) { if (state.status) { this.setStatus(this.initialStatus); } if (state.ui) { this.setUI(filterByKeys(this.initialUI, Object.keys(state.ui))); } if (state.props) { this.setProps(filterByKeys(this.initialProps, Object.keys(state.props))); } if (!isUndefined(state.value)) { await this.setValue(this.initialValue); } } public setValid(result: IValidateResult) { const { valid, message, status } = result; if (this.valid !== valid || this.message !== message || this.validStatus !== status) { this.triggerEvent('VALID_CHANGE'); } this.valid = valid; this.message = message; this.validStatus = status; } public setStatus(status: IFieldStatus) { this.children.forEach(field => field.setStatus(status)); if (!isEqual(this.status, status)) { this.status = status; this.triggerEvent('FIELD_CHANGE'); } } public setUI(ui: IFieldUI) { const filterUI = filterByKeys(this.ui, Object.keys(ui)); if (!isEqual(filterUI, ui)) { Object.assign(this.ui, ui); this.triggerEvent('FIELD_CHANGE'); } } public setProps(props: IFieldProps) { const filterProps = filterByKeys(this.props, Object.keys(props)); if (!isEqual(filterProps, props)) { Object.assign(this.props, props); this.triggerEvent('FIELD_CHANGE'); } } public async setValue(values: { [key: string]: any }) { if (!isEqual(this.value, values)) { await Promise.all(this.children.map(field => { const key = field.key; return field.setValue(values[key]); })); this.triggerEvent('VALUE_CHANGE'); await this.validate('change'); } } public getValue() { const { ignoreValues } = this.context; return this.children.filter(field => { if (ignoreValues.includes('hidden') && field.status === 'hidden') { return false; } if (ignoreValues.includes('preview') && field.status === 'preview') { 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) => { const key = field.key; result[key] = field.getValue(); return result; }, {}); } public async reset() { await Promise.all(this.children.map(field => field.reset())); this.triggerEvent('VALUE_CHANGE'); await this.validate('change'); this.emit('RESET'); } public async empty() { await Promise.all(this.children.map(field => field.empty())); this.triggerEvent('VALUE_CHANGE'); await this.validate('change'); this.emit('EMPTY'); } 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); } public off(event: IEvent, listener: (...args: any[]) => void) { this.emitter.removeListener(event, listener); } public triggerEvent(event: IEvent) { const key = this.key; const fieldGroup = this; // 增加代码可读性 const form = this.context; this.emit(event, key, fieldGroup, form); form.emit('FORM_CHANGE', event, key, fieldGroup, form); form.listenerManager.trigger(fieldGroup); } }