/* eslint-disable @typescript-eslint/no-this-alias */ import { IEvent, IFieldList, IFieldListParams, IFieldProps, IFieldStatus, IFieldUI, IForm, IListener, IListenerSet, IRule, IRuleStatus, IRuleTrigger, ISchema, IValidateResult, IValidator } from '@wowpic/xform-types'; import EventEmitter from 'events'; import Form from './form'; import { clone, computeRules2UI, computeUI2Rules, filterByKeys, isEqual, isUndefined } from './utils'; import Validator from './validator'; export default class FieldList implements IFieldList { 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 schema: ISchema; public children: IForm[]; public titles: { key: string; title: any }[]; public valid: boolean; public message: string; public validStatus: IRuleStatus; public context: IForm; private validator: IValidator; private emitter: EventEmitter; private timerMap: { [event: string]: any }; private counter: number; public get value() { return this.getValue(); } public initialValue: { [key: string]: any }[]; constructor(context: IForm, params: IFieldListParams) { this.context = context; this.initialize(params); } public async initialize(params: IFieldListParams) { this.schema = params.children || []; this.key = params.key; this.type = params.type || 'Array'; 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.counter = 0; this.emitter = new EventEmitter(); this.emitter.setMaxListeners(1000); this.timerMap = {}; this.titles = this.getTitles(this.schema); this.validator = new Validator(this, { rules: this.rules }); this.children = []; params.value && this.setValue(params.value); this.initialValue = params.value || []; await this.validate('change'); } private fillDefaultStatus(schema: ISchema, status: IFieldStatus) { return schema.map((item) => ({ ...item, status: item.status || status })); } private getTitles(schema: ISchema) { return schema.map((item) => { return { key: item.key, title: item.ui && item.ui.label ? item.ui.label : null, columnProps: item.ui && item.ui.columnProps ? clone(item.ui.columnProps) : null }; }); } public async copy(index: number) { const form = this.createForm(this.children[index].getValue()); this.children.splice(index + 1, 0, form); this.triggerEvent('CHILDREN_CHANGE'); this.triggerEvent('VALUE_CHANGE'); await this.validate('change'); return form; } public async add() { const form = this.createForm(); this.children.push(form); this.triggerEvent('CHILDREN_CHANGE'); await this.validate('change'); return form; } public async delete(index: number) { const result = this.children.splice(index, 1); this.triggerEvent('CHILDREN_CHANGE'); await this.validate('change'); return result.pop(); } public async move(fromIndex: number, toIndex: number) { const form = this.children[fromIndex]; this.children.splice(fromIndex, 1); this.children = this.children.slice(0, toIndex) .concat([form]) .concat(this.children.slice(toIndex)); await this.triggerEvent('CHILDREN_CHANGE'); return this.children; } public purify() { return this.children.map((form) => form.purify()); } public async validate(trigger: IRuleTrigger = 'submit') { if (trigger === 'submit') { await Promise.all( this.children.map((form) => Promise.all( form.fields.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 valid = ( await Promise.all(this.children.map((form) => form.validate())) ).every((v) => v); return valid; } 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((form) => form.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)) { this.children = values.map((_, index) => this.createForm(values[index]) ); this.triggerEvent('CHILDREN_CHANGE'); this.triggerEvent('VALUE_CHANGE'); await this.validate('change'); } } public getValue() { return this.children.map((form) => form.getValue()); } public async reset() { await this.setValue(this.initialValue); this.emit('RESET'); } public async empty() { await this.setValue([]); 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 fieldList = this; // 增加代码可读性 const form = this.context; this.emit(event, key, fieldList, form); form.emit('FORM_CHANGE', event, key, fieldList, form); form.listenerManager.trigger(fieldList); } private createForm(values?: { [name: string]: any }) { this.counter += 1; const form = new Form({ key: `${this.counter}`, schema: this.fillDefaultStatus(this.schema, this.status), ignoreValues: this.context.ignoreValues, values }); form.on('FORM_CHANGE', (event: IEvent) => { if (event === 'VALUE_CHANGE') { this.validate('change'); this.triggerEvent('VALUE_CHANGE'); } }); form.on('BLUR', () => { this.validate('blur'); this.emit('BLUR'); }); return form; } }