import set from 'lodash/set' import createFormField, { isFormField } from './createFormField' import { flattenFields, getErrorStrs, startsWith, } from './utils' function partOf (a, b) { return b.indexOf(a) === 0 && ['.', '['].indexOf(b[a.length]) !== -1 } class FieldsStore { constructor (fields) { this.fields = this.flattenFields(fields) this.fieldsMeta = {} } updateFields (fields) { this.fields = this.flattenFields(fields) } flattenFields (fields) { return flattenFields( fields, (_, node) => isFormField(node), 'You must wrap field data with `createFormField`.' ) } flattenRegisteredFields (fields) { const validFieldsName = this.getAllFieldsName() return flattenFields( fields, path => validFieldsName.indexOf(path) >= 0, 'You cannot set field before registering it.' ) } setFieldsInitialValue = (initialValues) => { const flattenedInitialValues = this.flattenRegisteredFields(initialValues) const fieldsMeta = this.fieldsMeta Object.keys(flattenedInitialValues).forEach(name => { if (fieldsMeta[name]) { this.setFieldMeta(name, { ...this.getFieldMeta(name), initialValue: flattenedInitialValues[name], }) } }) } setFields (fields) { const fieldsMeta = this.fieldsMeta const nowFields = { ...this.fields, ...fields, } const nowValues = {} Object.keys(fieldsMeta) .forEach((f) => { nowValues[f] = this.getValueFromFields(f, nowFields) }) Object.keys(nowValues).forEach((f) => { const value = nowValues[f] const fieldMeta = this.getFieldMeta(f) if (fieldMeta && fieldMeta.normalize) { const nowValue = fieldMeta.normalize(value, this.getValueFromFields(f, this.fields), nowValues) if (nowValue !== value) { nowFields[f] = { ...nowFields[f], value: nowValue, } } } }) this.fields = nowFields } resetFields (ns) { const { fields } = this const names = ns ? this.getValidFieldsFullName(ns) : this.getAllFieldsName() return names.reduce((acc, name) => { const field = fields[name] if (field && 'value' in field) { acc[name] = {} } return acc }, {}) } setFieldMeta (name, meta) { this.fieldsMeta[name] = meta } getFieldMeta (name) { this.fieldsMeta[name] = this.fieldsMeta[name] || {} return this.fieldsMeta[name] } getValueFromFields (name, fields) { const field = fields[name] if (field && 'value' in field) { return field.value } const fieldMeta = this.getFieldMeta(name) return fieldMeta && fieldMeta.initialValue } getAllValues = () => { const { fieldsMeta, fields } = this return Object.keys(fieldsMeta) .reduce((acc, name) => set(acc, name, this.getValueFromFields(name, fields)), {}) } getValidFieldsName () { const { fieldsMeta } = this return fieldsMeta ? Object.keys(fieldsMeta).filter(name => !this.getFieldMeta(name).hidden) : [] } getAllFieldsName () { const { fieldsMeta } = this return fieldsMeta ? Object.keys(fieldsMeta) : [] } getValidFieldsFullName (maybePartialName) { const maybePartialNames = Array.isArray(maybePartialName) ? maybePartialName : [maybePartialName] return this.getValidFieldsName() .filter(fullName => maybePartialNames.some(partialName => ( fullName === partialName || ( startsWith(fullName, partialName) && ['.', '['].indexOf(fullName[partialName.length]) >= 0 ) ))) } getFieldValuePropValue (fieldMeta) { const { name, getValueProps, valuePropName } = fieldMeta const field = this.getField(name) const fieldValue = 'value' in field ? field.value : fieldMeta.initialValue if (getValueProps) { return getValueProps(fieldValue) } return { [valuePropName]: fieldValue } } getField (name) { return { ...this.fields[name], name, } } getNotCollectedFields () { return this.getValidFieldsName() .filter(name => !this.fields[name]) .map(name => ({ name, dirty: false, value: this.getFieldMeta(name).initialValue, })) .reduce((acc, field) => set(acc, field.name, createFormField(field)), {}) } getNestedAllFields () { return Object.keys(this.fields) .reduce( (acc, name) => set(acc, name, createFormField(this.fields[name])), this.getNotCollectedFields() ) } getFieldMember (name, member) { return this.getField(name)[member] } getNestedFields (names, getter) { const fields = names || this.getValidFieldsName() return fields.reduce((acc, f) => set(acc, f, getter(f)), {}) } getNestedField (name, getter) { const fullNames = this.getValidFieldsFullName(name) if ( fullNames.length === 0 || // Not registered (fullNames.length === 1 && fullNames[0] === name) // Name already is full name. ) { return getter(name) } const isArrayValue = fullNames[0][name.length] === '[' const suffixNameStartIndex = isArrayValue ? name.length : name.length + 1 return fullNames .reduce( (acc, fullName) => set( acc, fullName.slice(suffixNameStartIndex), getter(fullName) ), isArrayValue ? [] : {} ) } getFieldsValue = (names) => { return this.getNestedFields(names, this.getFieldValue) } getFieldValue = (name) => { const { fields } = this return this.getNestedField(name, (fullName) => this.getValueFromFields(fullName, fields)) } getFieldsError = (names) => { return this.getNestedFields(names, this.getFieldError) } getFieldError = (name) => { return this.getNestedField( name, (fullName) => getErrorStrs(this.getFieldMember(fullName, 'errors')) ) } isFieldValidating = (name) => { return this.getFieldMember(name, 'validating') } isFieldsValidating = (ns) => { const names = ns || this.getValidFieldsName() return names.some((n) => this.isFieldValidating(n)) } isFieldTouched = (name) => { return this.getFieldMember(name, 'touched') } isFieldsTouched = (ns) => { const names = ns || this.getValidFieldsName() return names.some((n) => this.isFieldTouched(n)) } // @private // BG: `a` and `a.b` cannot be use in the same form isValidNestedFieldName (name) { const names = this.getAllFieldsName() return names.every(n => !partOf(n, name) && !partOf(name, n)) } clearField (name) { delete this.fields[name] delete this.fieldsMeta[name] } } export default function createFieldsStore (fields) { return new FieldsStore(fields) }