123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701 |
- import baseComponent from '../helpers/baseComponent'
- import classNames from '../helpers/libs/classNames'
- import warning from '../helpers/libs/warning'
- import { props } from './props'
- import createFieldsStore, {
- isNullValue,
- getValueFromEvent,
- getParams,
- transformRules,
- hasRules,
- normalizeValidateRules,
- getValidateTriggers,
- isEmptyObject,
- flattenArray,
- } from '../helpers/hooks/useFieldForm'
- import {
- FIELD_META_PROP,
- FIELD_DATA_PROP,
- DEFAULT_TRIGGER,
- } from '../helpers/shared/constants'
- import { get, set, eq } from '../helpers/shared/util'
- import AsyncValidator from '../helpers/libs/async-validator'
- baseComponent({
- useExport: true,
- relations: {
- '../field/index': {
- type: 'descendant',
- observer(e, { unlinked }) {
- this.renderFields[e.data.name] = unlinked === false
- this.callDebounceFn(this.changeValue)
- },
- },
- },
- properties: props,
- observers: {
- ['layout, validateMessages, requiredMarkStyle, asteriskText, requiredText, optionalText, disabled, readOnly'](...args) {
- this.changeFieldElem(this.data)
- },
- },
- computed: {
- classes: ['prefixCls', function(prefixCls) {
- const wrap = classNames(prefixCls)
- const footer = `${prefixCls}__footer`
- return {
- wrap,
- footer,
- }
- }],
- },
- methods: {
- changeFieldElem(props) {
- const {
- layout,
- validateMessages,
- requiredMarkStyle,
- asteriskText,
- requiredText,
- optionalText,
- disabled,
- readOnly,
- } = props
- const elements = this.getRelationsByName('../field/index')
- if (elements.length > 0) {
- const lastIndex = elements.length - 1
- elements.forEach((fieldElem, index) => {
- const isLast = index === lastIndex
- const context = {
- layout,
- validateMessages,
- requiredMarkStyle,
- asteriskText,
- requiredText,
- optionalText,
- disabled,
- readOnly,
- }
- fieldElem.changeContext(index, isLast, context)
- })
- }
- },
- changeValue() {
- this.changeFieldElem(this.data)
- this.clearUnlinkedFields()
- },
- saveRef(name, _, component) {
- if (!component) {
- const fieldMeta = this.fieldsStore.getFieldMeta(name);
- if (!fieldMeta.preserve) {
- // after destroy, delete data
- this.clearedFieldMetaCache[name] = {
- field: this.fieldsStore.getField(name),
- meta: fieldMeta,
- };
- this.clearField(name);
- }
- return;
- }
- this.recoverClearedField(name);
- this.instances[name] = component;
- },
- recoverClearedField(name) {
- if (this.clearedFieldMetaCache[name]) {
- this.fieldsStore.setFields({
- [name]: this.clearedFieldMetaCache[name].field,
- });
- this.fieldsStore.setFieldMeta(
- name,
- this.clearedFieldMetaCache[name].meta,
- );
- delete this.clearedFieldMetaCache[name];
- }
- },
- setFieldsAsErrors(name, fieldMeta) {
- const fieldNames = this.fieldsStore.getValidFieldsFullName(name)
- if (fieldNames.includes(name) && !hasRules(fieldMeta.validate)) {
- const nowField = this.fieldsStore.getField(name)
- if (nowField.errors) {
- this.fieldsStore.setFields({
- [name]: {
- ...nowField,
- errors: undefined,
- },
- })
- }
- }
- },
- /**
- * 清除已卸载的元素
- */
- clearUnlinkedFields() {
- const fieldList = this.fieldsStore.getAllFieldsName()
- const removedList = fieldList.filter((field) => {
- const fieldMeta = this.fieldsStore.getFieldMeta(field)
- return !this.renderFields[field] && !fieldMeta.preserve
- })
- if (removedList.length > 0) {
- removedList.forEach((name) => this.clearField(name))
- }
- },
- /**
- * 清除字段
- */
- clearField(name) {
- this.fieldsStore.clearField(name)
- delete this.renderFields[name]
- delete this.instances[name]
- delete this.cachedBind[name]
- },
- /**
- * 设置字段
- */
- setFields(maybeNestedFields, callback) {
- const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields)
- Object.keys(fields).forEach((name) => {
- this.fieldsStore.setFields({ [name]: fields[name] })
- const fieldMeta = this.fieldsStore.getFieldMeta(name)
- if (fieldMeta) {
- const { fieldElem, inputElem } = fieldMeta
- fieldElem.forceUpdate(inputElem, callback)
- }
- })
- // trigger onFieldsChange
- const changedFields = Object.keys(fields).reduce(
- (acc, name) => set(acc, name, this.fieldsStore.getField(name)),
- {},
- )
- const allFields = this.fieldsStore.getNestedAllFields()
- this.onFieldsChange(changedFields, allFields)
- },
- onCollectValidate(name, action, ...args) {
- const { field, fieldMeta } = this.onCollectCommon(name, action, args);
- const newField = {
- ...field,
- dirty: true,
- };
- this.fieldsStore.setFieldsAsDirty();
- this.validateFieldsInternal([newField], {
- action,
- options: {
- firstFields: !!fieldMeta.validateFirst,
- },
- });
- },
- /**
- * 同步子元素
- */
- onCollectCommon(name, action, args) {
- const field = this.fieldsStore.getField(name);
- const fieldMeta = this.fieldsStore.getFieldMeta(name)
- const { oriInputEvents, fieldElem } = fieldMeta
- // trigger inputElem func
- if (oriInputEvents && oriInputEvents[action]) {
- oriInputEvents[action](...args)
- }
- const value = getValueFromEvent(...args)
- const oldValue = this.fieldsStore.getFieldValue(name)
- // set field value
- if (value !== oldValue) {
- if (fieldElem.data.value !== value) {
- fieldElem.setData({ value })
- }
- // trigger onValuesChange
- const changedValues = { [name]: value }
- const allValues = this.fieldsStore.getAllValues()
- this.onValuesChange(changedValues, { ...allValues, ...changedValues })
-
- }
- return {
- name,
- field: {
- ...field,
- value,
- touched: true,
- },
- fieldMeta,
- }
- },
- /**
- * 同步子元素
- */
- onCollect(name_, action, ...args) {
- const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args)
- const { validate } = fieldMeta;
- this.fieldsStore.setFieldsAsDirty();
- const newField = {
- ...field,
- dirty: hasRules(validate),
- };
- this.setFields({ [name]: newField })
- },
- /**
- * 设置 value
- */
- setFieldsValue(changedValues, callback) {
- const { fieldsMeta } = this.fieldsStore;
- const values = this.fieldsStore.flattenRegisteredFields(changedValues);
- const newFields = Object.keys(values).reduce((acc, name) => {
- const isRegistered = fieldsMeta[name];
- if (isRegistered) {
- const value = values[name];
- acc[name] = {
- value,
- };
- }
- return acc;
- }, {});
- this.setFields(newFields, callback);
- const allValues = this.fieldsStore.getAllValues();
- this.onValuesChange(changedValues, allValues)
- },
- /**
- * 重置字段
- */
- resetFields(ns) {
- const names = typeof ns === 'undefined'
- ? ns : Array.isArray(ns)
- ? ns : [ns]
- const newFields = this.fieldsStore.resetFields(names)
- if (Object.keys(newFields).length > 0) {
- this.setFields(newFields)
- }
- if (ns) {
- const names = Array.isArray(ns) ? ns : [ns];
- names.forEach(name => delete this.clearedFieldMetaCache[name]);
- } else {
- this.clearedFieldMetaCache = {};
- }
- },
- validateFieldsInternal(
- fields,
- { fieldNames, action, options = {} },
- callback,
- ) {
- const allRules = {};
- const allValues = {};
- const allFields = {};
- const alreadyErrors = {};
- fields.forEach(field => {
- const name = field.name;
- if (options.force !== true && field.dirty === false) {
- if (field.errors) {
- set(alreadyErrors, name, { errors: field.errors });
- }
- return;
- }
- const fieldMeta = this.fieldsStore.getFieldMeta(name);
- const newField = {
- ...field,
- };
- newField.errors = undefined;
- newField.validating = true;
- newField.dirty = true;
- allRules[name] = this.getRules(fieldMeta, action);
- allValues[name] = newField.value;
- allFields[name] = newField;
- });
- this.setFields(allFields);
- // in case normalize
- Object.keys(allValues).forEach(f => {
- allValues[f] = this.fieldsStore.getFieldValue(f);
- });
- if (callback && isEmptyObject(allFields)) {
- callback(
- isEmptyObject(alreadyErrors) ? null : alreadyErrors,
- this.fieldsStore.getFieldsValue(fieldNames),
- );
- return;
- }
- const validator = new AsyncValidator(allRules);
- const { validateMessages } = this.data
- if (validateMessages) {
- validator.messages(validateMessages);
- }
- validator.validate(allValues, options, errors => {
- const errorsGroup = {
- ...alreadyErrors,
- };
- if (errors && errors.length) {
- errors.forEach(e => {
- const errorFieldName = e.field;
- let fieldName = errorFieldName;
-
- // Handle using array validation rule.
- Object.keys(allRules).some(ruleFieldName => {
- const rules = allRules[ruleFieldName] || [];
-
- // Exist if match rule
- if (ruleFieldName === errorFieldName) {
- fieldName = ruleFieldName;
- return true;
- }
-
- // Skip if not match array type
- if (
- rules.every(({ type }) => type !== 'array') ||
- errorFieldName.indexOf(`${ruleFieldName}.`) !== 0
- ) {
- return false;
- }
-
- // Exist if match the field name
- const restPath = errorFieldName.slice(ruleFieldName.length + 1);
- if (/^\d+$/.test(restPath)) {
- fieldName = ruleFieldName;
- return true;
- }
-
- return false;
- });
-
- const field = get(errorsGroup, fieldName);
- if (typeof field !== 'object' || Array.isArray(field)) {
- set(errorsGroup, fieldName, { errors: [] });
- }
- const fieldErrors = get(errorsGroup, fieldName.concat('.errors'));
- fieldErrors.push(e);
- });
- }
- const expired = [];
- const nowAllFields = {};
- Object.keys(allRules).forEach(name => {
- const fieldErrors = get(errorsGroup, name);
- const nowField = this.fieldsStore.getField(name);
- // avoid concurrency problems
- if (!eq(nowField.value, allValues[name])) {
- expired.push({
- name,
- });
- } else {
- nowField.errors = fieldErrors && fieldErrors.errors;
- nowField.value = allValues[name];
- nowField.validating = false;
- nowField.dirty = false;
- nowAllFields[name] = nowField;
- }
- });
- this.setFields(nowAllFields);
- if (callback) {
- if (expired.length) {
- expired.forEach(({ name }) => {
- const fieldErrors = [
- {
- message: `${name} need to revalidate`,
- field: name,
- },
- ];
- set(errorsGroup, name, {
- expired: true,
- errors: fieldErrors,
- });
- });
- }
-
- callback(
- isEmptyObject(errorsGroup) ? null : errorsGroup,
- this.fieldsStore.getFieldsValue(fieldNames),
- );
- }
- });
- },
- validateFields(ns, opt, cb) {
- const pending = new Promise((resolve, reject) => {
- const { names, options } = getParams(ns, opt, cb);
- let { callback } = getParams(ns, opt, cb);
- if (!callback || typeof callback === 'function') {
- const oldCb = callback;
- callback = (errors, values) => {
- if (oldCb) {
- oldCb(errors, values);
- }
- if (errors) {
- reject({ errors, values });
- } else {
- resolve(values);
- }
- };
- }
- const fieldNames = names
- ? this.fieldsStore.getValidFieldsFullName(names)
- : this.fieldsStore.getValidFieldsName();
- const fields = fieldNames
- .filter(name => {
- const fieldMeta = this.fieldsStore.getFieldMeta(name);
- return hasRules(fieldMeta.validate);
- })
- .map(name => {
- const field = this.fieldsStore.getField(name);
- field.value = this.fieldsStore.getFieldValue(name);
- return field;
- });
- if (!fields.length) {
- callback(null, this.fieldsStore.getFieldsValue(fieldNames));
- return;
- }
- if (!('firstFields' in options)) {
- options.firstFields = fieldNames.filter(name => {
- const fieldMeta = this.fieldsStore.getFieldMeta(name);
- return !!fieldMeta.validateFirst;
- });
- }
- this.validateFieldsInternal(
- fields,
- {
- fieldNames,
- options,
- },
- callback,
- );
- });
- pending.catch(e => {
- // eslint-disable-next-line no-console
- if (console.error) {
- // eslint-disable-next-line no-console
- console.error(e);
- }
- return e;
- });
- return pending;
- },
- getRules(fieldMeta, action) {
- const actionRules = fieldMeta.validate
- .filter(item => {
- return !action || item.trigger.indexOf(action) >= 0;
- })
- .map(item => item.rules);
- return flattenArray(actionRules);
- },
- getFieldInstance(name) {
- return this.instances[name];
- },
- getCacheBind(name, action, fn) {
- if (!this.cachedBind[name]) {
- this.cachedBind[name] = {};
- }
- const cache = this.cachedBind[name];
- if (!cache[action] || cache[action].oriFn !== fn) {
- cache[action] = {
- fn: fn.bind(this, name, action),
- oriFn: fn,
- };
- }
- return cache[action].fn;
- },
- getFieldDecorator(name, fieldData, fieldElem) {
- const props = this.getFieldProps(name, fieldData, fieldElem)
- return (inputElem) => {
- // We should put field in record if it is rendered
- this.renderFields[name] = true
- const fieldMeta = this.fieldsStore.getFieldMeta(name)
- const originalProps = inputElem.data
- fieldMeta.inputElem = inputElem
- // cache inputEvents
- if (!originalProps.oriInputEvents) {
- props.oriInputEvents = { ...originalProps.inputEvents }
- fieldMeta.oriInputEvents = { ...originalProps.inputEvents }
- }
- const decoratedFieldProps = {
- ...props,
- ...this.fieldsStore.getFieldValuePropValue(fieldMeta),
- }
- return decoratedFieldProps
- }
- },
- getFieldProps(name, fieldData, fieldElem) {
- delete this.clearedFieldMetaCache[name]
- const rules = transformRules(fieldData.rules)
- const {
- initialValue,
- trigger = DEFAULT_TRIGGER,
- valuePropName,
- validate = [],
- validateTrigger = [DEFAULT_TRIGGER],
- preserve,
- // rules,
- validateFirst,
- hidden,
- } = fieldData
- const fieldOption = {
- name,
- // initialValue,
- trigger,
- valuePropName,
- validate,
- validateTrigger,
- preserve,
- rules,
- validateFirst,
- hidden,
- }
- if (!isNullValue(initialValue)) {
- fieldOption.initialValue = initialValue
- }
- const inputProps = {
- ...this.fieldsStore.getFieldValuePropValue(fieldOption),
- inputEvents: {},
- }
-
- const fieldMeta = this.fieldsStore.getFieldMeta(name);
- if ('initialValue' in fieldOption) {
- fieldMeta.initialValue = fieldOption.initialValue;
- }
- fieldMeta.fieldElem = fieldElem
- const validateRules = normalizeValidateRules(
- validate,
- rules,
- validateTrigger,
- );
- const validateTriggers = getValidateTriggers(validateRules);
- validateTriggers.forEach(action => {
- if (inputProps.inputEvents[action]) { return };
- inputProps.inputEvents[action] = this.getCacheBind(
- name,
- action,
- this.onCollectValidate,
- )
- });
- // make sure that the value will be collect
- if (validateTriggers.indexOf(trigger) === -1) {
- inputProps.inputEvents[trigger] = this.getCacheBind(
- name,
- trigger,
- this.onCollect,
- )
- }
- const meta = {
- ...fieldMeta,
- ...fieldOption,
- validate: validateRules,
- }
- this.fieldsStore.setFieldMeta(name, meta)
- // fix errors
- this.setFieldsAsErrors(name, meta)
- const {
- fieldElem: __fieldElem,
- inputElem: __inputElem,
- oriInputEvents: __oriInputEvents,
- ...fieldMetaProp
- } = meta
- // inject field
- inputProps[FIELD_META_PROP] = fieldMetaProp
- inputProps[FIELD_DATA_PROP] = this.fieldsStore.getField(name);
- return inputProps
- },
- registerField(name, fieldElem) {
- const action = `${name}__ref`
- const ref = this.getCacheBind(name, action, this.saveRef)
- ref(fieldElem)
- return () => {
- ref(null)
- }
- },
- getInternalHooks(key) {
- if (key === 'FORM_HOOK_MARK') {
- return {
- registerField: this.registerField.bind(this),
- getFieldDecorator: this.getFieldDecorator.bind(this),
- }
- }
- warning(
- false,
- '`getInternalHooks` is internal usage of the <form />. Should not call directly.'
- )
- return null
- },
- /**
- * 表单对外暴露方法
- */
- expose() {
- return {
- getFieldsValue: this.getFieldsValue,
- getFieldValue: this.getFieldValue,
- setFieldsInitialValue: this.setFieldsInitialValue,
- getFieldsError: this.getFieldsError,
- getFieldError: this.getFieldError,
- isFieldValidating: this.isFieldValidating,
- isFieldsValidating: this.isFieldsValidating,
- isFieldsTouched: this.isFieldsTouched,
- isFieldTouched: this.isFieldTouched,
- setFieldsValue: this.setFieldsValue.bind(this),
- setFields: this.setFields.bind(this),
- resetFields: this.resetFields.bind(this),
- validateFields: this.validateFields.bind(this),
- getFieldInstance: this.getFieldInstance.bind(this),
- getInternalHooks: this.getInternalHooks.bind(this),
- }
- },
- /**
- * trigger onValuesChange
- */
- onValuesChange(changedValues, allValues) {
- this.triggerEvent('change', { form: this.expose(), changedValues, allValues })
- },
- /**
- * trigger onFieldsChange
- */
- onFieldsChange(changedFields, allFields) {
- this.triggerEvent('fieldsChange', { form: this.expose(), changedFields, allFields })
- },
- },
- created() {
- const methods = [
- 'getFieldsValue',
- 'getFieldValue',
- 'setFieldsInitialValue',
- 'getFieldsError',
- 'getFieldError',
- 'isFieldValidating',
- 'isFieldsValidating',
- 'isFieldsTouched',
- 'isFieldTouched',
- ]
- this.fieldsStore = createFieldsStore()
- this.renderFields = {}
- this.cachedBind = {}
- this.clearedFieldMetaCache = {}
- this.instances = {}
- methods.forEach((method) => {
- this[method] = (...args) => {
- return this.fieldsStore[method](...args)
- }
- })
- },
- })
|