feat: init vc-form component
parent
c04144afd9
commit
b5cd8f5714
|
@ -0,0 +1,3 @@
|
||||||
|
// export this package's api
|
||||||
|
import { createForm, createFormField } from './src/'
|
||||||
|
export { createForm, createFormField }
|
|
@ -0,0 +1,529 @@
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
import AsyncValidator from 'async-validator'
|
||||||
|
import warning from 'warning'
|
||||||
|
import get from 'lodash/get'
|
||||||
|
import set from 'lodash/set'
|
||||||
|
import createFieldsStore from './createFieldsStore'
|
||||||
|
import { cloneElement } from '../../_util/vnode'
|
||||||
|
import {
|
||||||
|
argumentContainer,
|
||||||
|
identity,
|
||||||
|
normalizeValidateRules,
|
||||||
|
getValidateTriggers,
|
||||||
|
getValueFromEvent,
|
||||||
|
hasRules,
|
||||||
|
getParams,
|
||||||
|
isEmptyObject,
|
||||||
|
flattenArray,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
const DEFAULT_TRIGGER = 'onChange'
|
||||||
|
|
||||||
|
function createBaseForm (option = {}, mixins = []) {
|
||||||
|
const {
|
||||||
|
validateMessages,
|
||||||
|
onFieldsChange,
|
||||||
|
onValuesChange,
|
||||||
|
mapProps = identity,
|
||||||
|
mapPropsToFields,
|
||||||
|
fieldNameProp,
|
||||||
|
fieldMetaProp,
|
||||||
|
fieldDataProp,
|
||||||
|
formPropName = 'form',
|
||||||
|
// @deprecated
|
||||||
|
withRef,
|
||||||
|
} = option
|
||||||
|
|
||||||
|
return function decorate (WrappedComponent) {
|
||||||
|
const Form = createReactClass({
|
||||||
|
mixins,
|
||||||
|
|
||||||
|
getInitialState () {
|
||||||
|
const fields = mapPropsToFields && mapPropsToFields(this.props)
|
||||||
|
this.fieldsStore = createFieldsStore(fields || {})
|
||||||
|
|
||||||
|
this.instances = {}
|
||||||
|
this.cachedBind = {}
|
||||||
|
this.clearedFieldMetaCache = {};
|
||||||
|
// HACK: https://github.com/ant-design/ant-design/issues/6406
|
||||||
|
['getFieldsValue',
|
||||||
|
'getFieldValue',
|
||||||
|
'setFieldsInitialValue',
|
||||||
|
'getFieldsError',
|
||||||
|
'getFieldError',
|
||||||
|
'isFieldValidating',
|
||||||
|
'isFieldsValidating',
|
||||||
|
'isFieldsTouched',
|
||||||
|
'isFieldTouched'].forEach(key => {
|
||||||
|
this[key] = (...args) => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'you should not use `ref` on enhanced form, please use `wrappedComponentRef`. ' +
|
||||||
|
'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.fieldsStore[key](...args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
submitting: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (mapPropsToFields) {
|
||||||
|
this.fieldsStore.updateFields(mapPropsToFields(nextProps))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollectCommon (name, action, args) {
|
||||||
|
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||||
|
if (fieldMeta[action]) {
|
||||||
|
fieldMeta[action](...args)
|
||||||
|
} else if (fieldMeta.originalProps && fieldMeta.originalProps[action]) {
|
||||||
|
fieldMeta.originalProps[action](...args)
|
||||||
|
}
|
||||||
|
const value = fieldMeta.getValueFromEvent
|
||||||
|
? fieldMeta.getValueFromEvent(...args)
|
||||||
|
: getValueFromEvent(...args)
|
||||||
|
if (onValuesChange && value !== this.fieldsStore.getFieldValue(name)) {
|
||||||
|
const valuesAll = this.fieldsStore.getAllValues()
|
||||||
|
const valuesAllSet = {}
|
||||||
|
valuesAll[name] = value
|
||||||
|
Object.keys(valuesAll).forEach(key => set(valuesAllSet, key, valuesAll[key]))
|
||||||
|
onValuesChange(this.props, set({}, name, value), valuesAllSet)
|
||||||
|
}
|
||||||
|
const field = this.fieldsStore.getField(name)
|
||||||
|
return ({ name, field: { ...field, value, touched: true }, fieldMeta })
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollect (name_, action, ...args) {
|
||||||
|
const { name, field, fieldMeta } = this.onCollectCommon(name_, action, args)
|
||||||
|
const { validate } = fieldMeta
|
||||||
|
const newField = {
|
||||||
|
...field,
|
||||||
|
dirty: hasRules(validate),
|
||||||
|
}
|
||||||
|
this.setFields({
|
||||||
|
[name]: newField,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollectValidate (name_, action, ...args) {
|
||||||
|
const { field, fieldMeta } = this.onCollectCommon(name_, action, args)
|
||||||
|
const newField = {
|
||||||
|
...field,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
this.validateFieldsInternal([newField], {
|
||||||
|
action,
|
||||||
|
options: {
|
||||||
|
firstFields: !!fieldMeta.validateFirst,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getCacheBind (name, action, fn) {
|
||||||
|
if (!this.cachedBind[name]) {
|
||||||
|
this.cachedBind[name] = {}
|
||||||
|
}
|
||||||
|
const cache = this.cachedBind[name]
|
||||||
|
if (!cache[action]) {
|
||||||
|
cache[action] = fn.bind(this, name, action)
|
||||||
|
}
|
||||||
|
return cache[action]
|
||||||
|
},
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getFieldDecorator (name, fieldOption) {
|
||||||
|
const props = this.getFieldProps(name, fieldOption)
|
||||||
|
return (fieldElem) => {
|
||||||
|
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||||
|
const originalProps = fieldElem.props
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const valuePropName = fieldMeta.valuePropName
|
||||||
|
warning(
|
||||||
|
!(valuePropName in originalProps),
|
||||||
|
`\`getFieldDecorator\` will override \`${valuePropName}\`, ` +
|
||||||
|
`so please don't set \`${valuePropName}\` directly ` +
|
||||||
|
`and use \`setFieldsValue\` to set it.`
|
||||||
|
)
|
||||||
|
const defaultValuePropName =
|
||||||
|
`default${valuePropName[0].toUpperCase()}${valuePropName.slice(1)}`
|
||||||
|
warning(
|
||||||
|
!(defaultValuePropName in originalProps),
|
||||||
|
`\`${defaultValuePropName}\` is invalid ` +
|
||||||
|
`for \`getFieldDecorator\` will set \`${valuePropName}\`,` +
|
||||||
|
` please use \`option.initialValue\` instead.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fieldMeta.originalProps = originalProps
|
||||||
|
fieldMeta.ref = fieldElem.ref
|
||||||
|
return cloneElement(fieldElem, {
|
||||||
|
...props,
|
||||||
|
...this.fieldsStore.getFieldValuePropValue(fieldMeta),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getFieldProps (name, usersFieldOption = {}) {
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('Must call `getFieldProps` with valid name string!')
|
||||||
|
}
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
warning(
|
||||||
|
this.fieldsStore.isValidNestedFieldName(name),
|
||||||
|
'One field name cannot be part of another, e.g. `a` and `a.b`.'
|
||||||
|
)
|
||||||
|
warning(
|
||||||
|
!('exclusive' in usersFieldOption),
|
||||||
|
'`option.exclusive` of `getFieldProps`|`getFieldDecorator` had been remove.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.clearedFieldMetaCache[name]
|
||||||
|
|
||||||
|
const fieldOption = {
|
||||||
|
name,
|
||||||
|
trigger: DEFAULT_TRIGGER,
|
||||||
|
valuePropName: 'value',
|
||||||
|
validate: [],
|
||||||
|
...usersFieldOption,
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
rules,
|
||||||
|
trigger,
|
||||||
|
validateTrigger = trigger,
|
||||||
|
validate,
|
||||||
|
} = fieldOption
|
||||||
|
|
||||||
|
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||||
|
if ('initialValue' in fieldOption) {
|
||||||
|
fieldMeta.initialValue = fieldOption.initialValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputProps = {
|
||||||
|
...this.fieldsStore.getFieldValuePropValue(fieldOption),
|
||||||
|
ref: this.getCacheBind(name, `${name}__ref`, this.saveRef),
|
||||||
|
}
|
||||||
|
if (fieldNameProp) {
|
||||||
|
inputProps[fieldNameProp] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateRules = normalizeValidateRules(validate, rules, validateTrigger)
|
||||||
|
const validateTriggers = getValidateTriggers(validateRules)
|
||||||
|
validateTriggers.forEach((action) => {
|
||||||
|
if (inputProps[action]) return
|
||||||
|
inputProps[action] = this.getCacheBind(name, action, this.onCollectValidate)
|
||||||
|
})
|
||||||
|
|
||||||
|
// make sure that the value will be collect
|
||||||
|
if (trigger && validateTriggers.indexOf(trigger) === -1) {
|
||||||
|
inputProps[trigger] = this.getCacheBind(name, trigger, this.onCollect)
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
...fieldMeta,
|
||||||
|
...fieldOption,
|
||||||
|
validate: validateRules,
|
||||||
|
}
|
||||||
|
this.fieldsStore.setFieldMeta(name, meta)
|
||||||
|
if (fieldMetaProp) {
|
||||||
|
inputProps[fieldMetaProp] = meta
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldDataProp) {
|
||||||
|
inputProps[fieldDataProp] = this.fieldsStore.getField(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputProps
|
||||||
|
},
|
||||||
|
|
||||||
|
getFieldInstance (name) {
|
||||||
|
return this.instances[name]
|
||||||
|
},
|
||||||
|
|
||||||
|
getRules (fieldMeta, action) {
|
||||||
|
const actionRules = fieldMeta.validate.filter((item) => {
|
||||||
|
return !action || item.trigger.indexOf(action) >= 0
|
||||||
|
}).map((item) => item.rules)
|
||||||
|
return flattenArray(actionRules)
|
||||||
|
},
|
||||||
|
|
||||||
|
setFields (maybeNestedFields, callback) {
|
||||||
|
const fields = this.fieldsStore.flattenRegisteredFields(maybeNestedFields)
|
||||||
|
this.fieldsStore.setFields(fields)
|
||||||
|
if (onFieldsChange) {
|
||||||
|
const changedFields = Object.keys(fields)
|
||||||
|
.reduce((acc, name) => set(acc, name, this.fieldsStore.getField(name)), {})
|
||||||
|
onFieldsChange(this.props, changedFields, this.fieldsStore.getNestedAllFields())
|
||||||
|
}
|
||||||
|
this.forceUpdate(callback)
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFields (ns) {
|
||||||
|
const newFields = this.fieldsStore.resetFields(ns)
|
||||||
|
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 = {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 (process.env.NODE_ENV !== 'production') {
|
||||||
|
warning(
|
||||||
|
isRegistered,
|
||||||
|
'Cannot use `setFieldsValue` until ' +
|
||||||
|
'you use `getFieldDecorator` or `getFieldProps` to register it.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isRegistered) {
|
||||||
|
const value = values[name]
|
||||||
|
acc[name] = {
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
this.setFields(newFields, callback)
|
||||||
|
if (onValuesChange) {
|
||||||
|
const allValues = this.fieldsStore.getAllValues()
|
||||||
|
onValuesChange(this.props, changedValues, allValues)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveRef (name, _, component) {
|
||||||
|
if (!component) {
|
||||||
|
// after destroy, delete data
|
||||||
|
this.clearedFieldMetaCache[name] = {
|
||||||
|
field: this.fieldsStore.getField(name),
|
||||||
|
meta: this.fieldsStore.getFieldMeta(name),
|
||||||
|
}
|
||||||
|
this.fieldsStore.clearField(name)
|
||||||
|
delete this.instances[name]
|
||||||
|
delete this.cachedBind[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.recoverClearedField(name)
|
||||||
|
const fieldMeta = this.fieldsStore.getFieldMeta(name)
|
||||||
|
if (fieldMeta) {
|
||||||
|
const ref = fieldMeta.ref
|
||||||
|
if (ref) {
|
||||||
|
if (typeof ref === 'string') {
|
||||||
|
throw new Error(`can not set ref string for ${name}`)
|
||||||
|
}
|
||||||
|
ref(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.instances[name] = component
|
||||||
|
},
|
||||||
|
|
||||||
|
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)
|
||||||
|
if (validateMessages) {
|
||||||
|
validator.messages(validateMessages)
|
||||||
|
}
|
||||||
|
validator.validate(allValues, options, (errors) => {
|
||||||
|
const errorsGroup = {
|
||||||
|
...alreadyErrors,
|
||||||
|
}
|
||||||
|
if (errors && errors.length) {
|
||||||
|
errors.forEach((e) => {
|
||||||
|
const fieldName = e.field
|
||||||
|
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 (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 { names, callback, options } = getParams(ns, opt, cb)
|
||||||
|
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) {
|
||||||
|
if (callback) {
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
|
||||||
|
isSubmitting () {
|
||||||
|
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'`isSubmitting` is deprecated. ' +
|
||||||
|
'Actually, it\'s more convenient to handle submitting status by yourself.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.state.submitting
|
||||||
|
},
|
||||||
|
|
||||||
|
submit (callback) {
|
||||||
|
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'`submit` is deprecated.' +
|
||||||
|
'Actually, it\'s more convenient to handle submitting status by yourself.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const fn = () => {
|
||||||
|
this.setState({
|
||||||
|
submitting: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
submitting: true,
|
||||||
|
})
|
||||||
|
callback(fn)
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { wrappedComponentRef, ...restProps } = this.props
|
||||||
|
const formProps = {
|
||||||
|
[formPropName]: this.getForm(),
|
||||||
|
}
|
||||||
|
if (withRef) {
|
||||||
|
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'`withRef` is deprecated, please use `wrappedComponentRef` instead. ' +
|
||||||
|
'See: https://github.com/react-component/form#note-use-wrappedcomponentref-instead-of-withref-after-rc-form140'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
formProps.ref = 'wrappedComponent'
|
||||||
|
} else if (wrappedComponentRef) {
|
||||||
|
formProps.ref = wrappedComponentRef
|
||||||
|
}
|
||||||
|
const props = mapProps.call(this, {
|
||||||
|
...formProps,
|
||||||
|
...restProps,
|
||||||
|
})
|
||||||
|
return <WrappedComponent {...props}/>
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return argumentContainer(Form, WrappedComponent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createBaseForm
|
|
@ -0,0 +1,104 @@
|
||||||
|
import scrollIntoView from 'dom-scroll-into-view'
|
||||||
|
import has from 'lodash/has'
|
||||||
|
import createBaseForm from './createBaseForm'
|
||||||
|
import { mixin as formMixin } from './createForm'
|
||||||
|
import { getParams } from './utils'
|
||||||
|
|
||||||
|
function computedStyle (el, prop) {
|
||||||
|
const getComputedStyle = window.getComputedStyle
|
||||||
|
const style =
|
||||||
|
// If we have getComputedStyle
|
||||||
|
getComputedStyle
|
||||||
|
// Query it
|
||||||
|
// TODO: From CSS-Query notes, we might need (node, null) for FF
|
||||||
|
? getComputedStyle(el)
|
||||||
|
|
||||||
|
// Otherwise, we are in IE and use currentStyle
|
||||||
|
: el.currentStyle
|
||||||
|
if (style) {
|
||||||
|
return style[
|
||||||
|
// Switch to camelCase for CSSOM
|
||||||
|
// DEV: Grabbed from jQuery
|
||||||
|
// https://github.com/jquery/jquery/blob/1.9-stable/src/css.js#L191-L194
|
||||||
|
// https://github.com/jquery/jquery/blob/1.9-stable/src/core.js#L593-L597
|
||||||
|
prop.replace(/-(\w)/gi, (word, letter) => {
|
||||||
|
return letter.toUpperCase()
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function getScrollableContainer (n) {
|
||||||
|
let node = n
|
||||||
|
let nodeName
|
||||||
|
/* eslint no-cond-assign:0 */
|
||||||
|
while ((nodeName = node.nodeName.toLowerCase()) !== 'body') {
|
||||||
|
const overflowY = computedStyle(node, 'overflowY')
|
||||||
|
// https://stackoverflow.com/a/36900407/3040605
|
||||||
|
if (
|
||||||
|
node !== n &&
|
||||||
|
(overflowY === 'auto' || overflowY === 'scroll') &&
|
||||||
|
node.scrollHeight > node.clientHeight
|
||||||
|
) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
node = node.parentNode
|
||||||
|
}
|
||||||
|
return nodeName === 'body' ? node.ownerDocument : node
|
||||||
|
}
|
||||||
|
|
||||||
|
const mixin = {
|
||||||
|
getForm () {
|
||||||
|
return {
|
||||||
|
...formMixin.getForm.call(this),
|
||||||
|
validateFieldsAndScroll: this.validateFieldsAndScroll,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
validateFieldsAndScroll (ns, opt, cb) {
|
||||||
|
const { names, callback, options } = getParams(ns, opt, cb)
|
||||||
|
|
||||||
|
const newCb = (error, values) => {
|
||||||
|
if (error) {
|
||||||
|
const validNames = this.fieldsStore.getValidFieldsName()
|
||||||
|
let firstNode
|
||||||
|
let firstTop
|
||||||
|
for (const name of validNames) {
|
||||||
|
if (has(error, name)) {
|
||||||
|
const instance = this.getFieldInstance(name)
|
||||||
|
if (instance) {
|
||||||
|
const node = instance.$el
|
||||||
|
const top = node.getBoundingClientRect().top
|
||||||
|
if (firstTop === undefined || firstTop > top) {
|
||||||
|
firstTop = top
|
||||||
|
firstNode = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstNode) {
|
||||||
|
const c = options.container || getScrollableContainer(firstNode)
|
||||||
|
scrollIntoView(firstNode, c, {
|
||||||
|
onlyScrollIfNeeded: true,
|
||||||
|
...options.scroll,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(error, values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.validateFields(names, options, newCb)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDOMForm (option) {
|
||||||
|
return createBaseForm({
|
||||||
|
...option,
|
||||||
|
}, [mixin])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createDOMForm
|
|
@ -0,0 +1,261 @@
|
||||||
|
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)
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import createBaseForm from './createBaseForm'
|
||||||
|
|
||||||
|
export const mixin = {
|
||||||
|
getForm () {
|
||||||
|
return {
|
||||||
|
getFieldsValue: this.fieldsStore.getFieldsValue,
|
||||||
|
getFieldValue: this.fieldsStore.getFieldValue,
|
||||||
|
getFieldInstance: this.getFieldInstance,
|
||||||
|
setFieldsValue: this.setFieldsValue,
|
||||||
|
setFields: this.setFields,
|
||||||
|
setFieldsInitialValue: this.fieldsStore.setFieldsInitialValue,
|
||||||
|
getFieldDecorator: this.getFieldDecorator,
|
||||||
|
getFieldProps: this.getFieldProps,
|
||||||
|
getFieldsError: this.fieldsStore.getFieldsError,
|
||||||
|
getFieldError: this.fieldsStore.getFieldError,
|
||||||
|
isFieldValidating: this.fieldsStore.isFieldValidating,
|
||||||
|
isFieldsValidating: this.fieldsStore.isFieldsValidating,
|
||||||
|
isFieldsTouched: this.fieldsStore.isFieldsTouched,
|
||||||
|
isFieldTouched: this.fieldsStore.isFieldTouched,
|
||||||
|
isSubmitting: this.isSubmitting,
|
||||||
|
submit: this.submit,
|
||||||
|
validateFields: this.validateFields,
|
||||||
|
resetFields: this.resetFields,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function createForm (options) {
|
||||||
|
return createBaseForm(options, [mixin])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createForm
|
|
@ -0,0 +1,16 @@
|
||||||
|
class Field {
|
||||||
|
constructor (fields) {
|
||||||
|
Object.assign(this, fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFormField (obj) {
|
||||||
|
return obj instanceof Field
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createFormField (field) {
|
||||||
|
if (isFormField(field)) {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
return new Field(field)
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// export this package's api
|
||||||
|
import createForm from './createForm'
|
||||||
|
import createFormField from './createFormField'
|
||||||
|
import formShape from './propTypes'
|
||||||
|
|
||||||
|
export { createForm, createFormField, formShape }
|
|
@ -0,0 +1,24 @@
|
||||||
|
import PropTypes from '../../_util/vue-types'
|
||||||
|
|
||||||
|
const formShape = PropTypes.shape({
|
||||||
|
getFieldsValue: PropTypes.func,
|
||||||
|
getFieldValue: PropTypes.func,
|
||||||
|
getFieldInstance: PropTypes.func,
|
||||||
|
setFieldsValue: PropTypes.func,
|
||||||
|
setFields: PropTypes.func,
|
||||||
|
setFieldsInitialValue: PropTypes.func,
|
||||||
|
getFieldDecorator: PropTypes.func,
|
||||||
|
getFieldProps: PropTypes.func,
|
||||||
|
getFieldsError: PropTypes.func,
|
||||||
|
getFieldError: PropTypes.func,
|
||||||
|
isFieldValidating: PropTypes.func,
|
||||||
|
isFieldsValidating: PropTypes.func,
|
||||||
|
isFieldsTouched: PropTypes.func,
|
||||||
|
isFieldTouched: PropTypes.func,
|
||||||
|
isSubmitting: PropTypes.func,
|
||||||
|
submit: PropTypes.func,
|
||||||
|
validateFields: PropTypes.func,
|
||||||
|
resetFields: PropTypes.func,
|
||||||
|
}).loose
|
||||||
|
|
||||||
|
export default formShape
|
|
@ -0,0 +1,153 @@
|
||||||
|
import hoistStatics from 'hoist-non-react-statics'
|
||||||
|
|
||||||
|
function getDisplayName (WrappedComponent) {
|
||||||
|
return WrappedComponent.displayName || WrappedComponent.name || 'WrappedComponent'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function argumentContainer (Container, WrappedComponent) {
|
||||||
|
/* eslint no-param-reassign:0 */
|
||||||
|
Container.displayName = `Form(${getDisplayName(WrappedComponent)})`
|
||||||
|
Container.WrappedComponent = WrappedComponent
|
||||||
|
return hoistStatics(Container, WrappedComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function identity (obj) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenArray (arr) {
|
||||||
|
return Array.prototype.concat.apply([], arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeTraverse (path = '', tree, isLeafNode, errorMessage, callback) {
|
||||||
|
if (isLeafNode(path, tree)) {
|
||||||
|
callback(path, tree)
|
||||||
|
} else if (tree === undefined) {
|
||||||
|
return
|
||||||
|
} else if (Array.isArray(tree)) {
|
||||||
|
tree.forEach((subTree, index) => treeTraverse(
|
||||||
|
`${path}[${index}]`,
|
||||||
|
subTree,
|
||||||
|
isLeafNode,
|
||||||
|
errorMessage,
|
||||||
|
callback
|
||||||
|
))
|
||||||
|
} else { // It's object and not a leaf node
|
||||||
|
if (typeof tree !== 'object') {
|
||||||
|
console.error(errorMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Object.keys(tree).forEach(subTreeKey => {
|
||||||
|
const subTree = tree[subTreeKey]
|
||||||
|
treeTraverse(
|
||||||
|
`${path}${path ? '.' : ''}${subTreeKey}`,
|
||||||
|
subTree,
|
||||||
|
isLeafNode,
|
||||||
|
errorMessage,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function flattenFields (maybeNestedFields, isLeafNode, errorMessage) {
|
||||||
|
const fields = {}
|
||||||
|
treeTraverse(undefined, maybeNestedFields, isLeafNode, errorMessage, (path, node) => {
|
||||||
|
fields[path] = node
|
||||||
|
})
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeValidateRules (validate, rules, validateTrigger) {
|
||||||
|
const validateRules = validate.map((item) => {
|
||||||
|
const newItem = {
|
||||||
|
...item,
|
||||||
|
trigger: item.trigger || [],
|
||||||
|
}
|
||||||
|
if (typeof newItem.trigger === 'string') {
|
||||||
|
newItem.trigger = [newItem.trigger]
|
||||||
|
}
|
||||||
|
return newItem
|
||||||
|
})
|
||||||
|
if (rules) {
|
||||||
|
validateRules.push({
|
||||||
|
trigger: validateTrigger ? [].concat(validateTrigger) : [],
|
||||||
|
rules,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return validateRules
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValidateTriggers (validateRules) {
|
||||||
|
return validateRules
|
||||||
|
.filter(item => !!item.rules && item.rules.length)
|
||||||
|
.map(item => item.trigger)
|
||||||
|
.reduce((pre, curr) => pre.concat(curr), [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getValueFromEvent (e) {
|
||||||
|
// To support custom element
|
||||||
|
if (!e || !e.target) {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
const { target } = e
|
||||||
|
return target.type === 'checkbox' ? target.checked : target.value
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getErrorStrs (errors) {
|
||||||
|
if (errors) {
|
||||||
|
return errors.map((e) => {
|
||||||
|
if (e && e.message) {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getParams (ns, opt, cb) {
|
||||||
|
let names = ns
|
||||||
|
let options = opt
|
||||||
|
let callback = cb
|
||||||
|
if (cb === undefined) {
|
||||||
|
if (typeof names === 'function') {
|
||||||
|
callback = names
|
||||||
|
options = {}
|
||||||
|
names = undefined
|
||||||
|
} else if (Array.isArray(names)) {
|
||||||
|
if (typeof options === 'function') {
|
||||||
|
callback = options
|
||||||
|
options = {}
|
||||||
|
} else {
|
||||||
|
options = options || {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback = options
|
||||||
|
options = names || {}
|
||||||
|
names = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
names,
|
||||||
|
options,
|
||||||
|
callback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmptyObject (obj) {
|
||||||
|
return Object.keys(obj).length === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasRules (validate) {
|
||||||
|
if (validate) {
|
||||||
|
return validate.some((item) => {
|
||||||
|
return item.rules && item.rules.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startsWith (str, prefix) {
|
||||||
|
return str.lastIndexOf(prefix, 0) === 0
|
||||||
|
}
|
Loading…
Reference in New Issue