From c46985373387e2850c66963ba32b681d6cf3ebe6 Mon Sep 17 00:00:00 2001 From: tanjinzhou <415800467@qq.com> Date: Tue, 14 Jul 2020 18:39:43 +0800 Subject: [PATCH] feat: update form --- antdv-demo | 2 +- components/form-model/Form.jsx | 121 ++++++++++-------------- components/form-model/FormItem.jsx | 99 +++++++++++--------- components/form-model/utils.js | 143 ----------------------------- package.json | 1 + 5 files changed, 108 insertions(+), 258 deletions(-) delete mode 100644 components/form-model/utils.js diff --git a/antdv-demo b/antdv-demo index f836704c6..3f77f2961 160000 --- a/antdv-demo +++ b/antdv-demo @@ -1 +1 @@ -Subproject commit f836704c60bf7e647e9e59f4a136024a960698b3 +Subproject commit 3f77f2961251a4a7c185dab85663a274eaea65b3 diff --git a/components/form-model/Form.jsx b/components/form-model/Form.jsx index 79006249f..111b19db6 100755 --- a/components/form-model/Form.jsx +++ b/components/form-model/Form.jsx @@ -1,5 +1,4 @@ import { inject, provide } from 'vue'; -// import scrollIntoView from 'dom-scroll-into-view'; import PropTypes from '../_util/vue-types'; import classNames from 'classnames'; import { ColProps } from '../grid/Col'; @@ -12,6 +11,8 @@ import { getNamePath, containsNamePath } from './utils/valueUtil'; import { defaultValidateMessages } from './utils/messages'; import { allPromiseFinish } from './utils/asyncUtil'; import { toArray } from './utils/typeUtil'; +import isEqual from 'lodash/isEqual'; +import scrollIntoView from 'scroll-into-view-if-needed'; export const FormProps = { layout: PropTypes.oneOf(['horizontal', 'inline', 'vertical']), @@ -57,6 +58,10 @@ export const ValidationRule = { validator: PropTypes.func, }; +function isEqualName(name1, name2) { + return isEqual(toArray(name1), toArray(name2)); +} + const Form = { name: 'AFormModel', inheritAttrs: false, @@ -80,7 +85,7 @@ const Form = { watch: { rules() { if (this.validateOnRuleChange) { - this.validate(() => {}); + this.validateFields(); } }, }, @@ -96,7 +101,7 @@ const Form = { } }, removeField(field) { - if (field.prop) { + if (field.fieldName) { this.fields.splice(this.fields.indexOf(field), 1); } }, @@ -107,37 +112,34 @@ const Form = { const res = this.validateFields(); res .then(values => { - // eslint-disable-next-line no-console - console.log('values', values); this.$emit('finish', values); }) .catch(errors => { - // eslint-disable-next-line no-console - console.log('errors', errors); this.handleFinishFailed(errors); }); }, - resetFields(props = []) { + getFieldsByNameList(nameList) { + const provideNameList = !!nameList; + const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : []; + if (!provideNameList) { + return this.fields; + } else { + return this.fields.filter( + field => namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName)) > -1, + ); + } + }, + resetFields(name) { if (!this.model) { - warning(false, 'FormModel', 'model is required for resetFields to work.'); + warning(false, 'Form', 'model is required for resetFields to work.'); return; } - const fields = props.length - ? typeof props === 'string' - ? this.fields.filter(field => props === field.prop) - : this.fields.filter(field => props.indexOf(field.prop) > -1) - : this.fields; - fields.forEach(field => { + this.getFieldsByNameList(name).forEach(field => { field.resetField(); }); }, - clearValidate(props = []) { - const fields = props.length - ? typeof props === 'string' - ? this.fields.filter(field => props === field.prop) - : this.fields.filter(field => props.indexOf(field.prop) > -1) - : this.fields; - fields.forEach(field => { + clearValidate(name) { + this.getFieldsByNameList(name).forEach(field => { field.clearValidate(); }); }, @@ -150,51 +152,35 @@ const Form = { }, validate() { return this.validateField(...arguments); - - // if (!this.model) { - // warning(false, 'FormModel', 'model is required for resetFields to work.'); - // return; - // } - // let promise; - // // if no callback, return promise - // if (typeof callback !== 'function' && window.Promise) { - // promise = new window.Promise((resolve, reject) => { - // callback = function(valid) { - // valid ? resolve(valid) : reject(valid); - // }; - // }); - // } - // let valid = true; - // let count = 0; - // // 如果需要验证的fields为空,调用验证时立刻返回callback - // if (this.fields.length === 0 && callback) { - // callback(true); - // } - // let invalidFields = {}; - // this.fields.forEach(field => { - // field.validate('', (message, field) => { - // if (message) { - // valid = false; - // } - // invalidFields = Object.assign({}, invalidFields, field); - // if (typeof callback === 'function' && ++count === this.fields.length) { - // callback(valid, invalidFields); - // } - // }); - // }); - // if (promise) { - // return promise; - // } }, - scrollToField() {}, - // TODO + scrollToField(name, options = {}) { + const fields = this.getFieldsByNameList([name]); + if (fields.length) { + const fieldId = fields[0].fieldId; + const node = fieldId ? document.getElementById(fieldId) : null; + + if (node) { + scrollIntoView(node, { + scrollMode: 'if-needed', + block: 'nearest', + ...options, + }); + } + } + }, // eslint-disable-next-line no-unused-vars - getFieldsValue(nameList) { + getFieldsValue(nameList = true) { const values = {}; - this.fields.forEach(({ prop, fieldValue }) => { - values[prop] = fieldValue; + this.fields.forEach(({ fieldName, fieldValue }) => { + values[fieldName] = fieldValue; }); - return values; + if (nameList === true) { + return values; + } else { + const res = {}; + toArray(nameList).forEach(namePath => (res[namePath] = values[namePath])); + return res; + } }, validateFields(nameList, options) { if (!this.model) { @@ -247,15 +233,6 @@ const Form = { const summaryPromise = allPromiseFinish(promiseList); this.lastValidatePromise = summaryPromise; - // // Notify fields with rule that validate has finished and need update - // summaryPromise - // .catch(results => results) - // .then(results => { - // const resultNamePathList = results.map(({ name }) => name); - // // eslint-disable-next-line no-console - // console.log(resultNamePathList); - // }); - const returnPromise = summaryPromise .then(() => { if (this.lastValidatePromise === summaryPromise) { diff --git a/components/form-model/FormItem.jsx b/components/form-model/FormItem.jsx index 8b485cab1..d3e21f8b2 100644 --- a/components/form-model/FormItem.jsx +++ b/components/form-model/FormItem.jsx @@ -5,7 +5,7 @@ import classNames from 'classnames'; import getTransitionProps from '../_util/getTransitionProps'; import Row from '../grid/Row'; import Col, { ColProps } from '../grid/Col'; -import { +import hasProp, { initDefaultProps, findDOMNode, getComponent, @@ -24,6 +24,7 @@ import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import { validateRules } from './utils/validateUtil'; import { getNamePath } from './utils/valueUtil'; import { toArray } from './utils/typeUtil'; +import { warning } from '../vc-util/warning'; const iconMap = { success: CheckCircleFilled, @@ -32,29 +33,35 @@ const iconMap = { validating: LoadingOutlined, }; -function getPropByPath(obj, path, strict) { +function getPropByPath(obj, namePathList, strict) { let tempObj = obj; - path = path.replace(/\[(\w+)\]/g, '.$1'); - path = path.replace(/^\./, ''); - let keyArr = path.split('.'); + const keyArr = namePathList; let i = 0; - for (let len = keyArr.length; i < len - 1; ++i) { - if (!tempObj && !strict) break; - let key = keyArr[i]; - if (key in tempObj) { - tempObj = tempObj[key]; - } else { - if (strict) { - throw new Error('please transfer a valid prop path to form item!'); + try { + for (let len = keyArr.length; i < len - 1; ++i) { + if (!tempObj && !strict) break; + let key = keyArr[i]; + if (key in tempObj) { + tempObj = tempObj[key]; + } else { + if (strict) { + throw Error('please transfer a valid name path to form item!'); + } + break; } - break; } + if (strict && !tempObj) { + throw Error('please transfer a valid name path to form item!'); + } + } catch (error) { + console.error('please transfer a valid name path to form item!'); } + return { o: tempObj, k: keyArr[i], - v: tempObj ? tempObj[keyArr[i]] : null, + v: tempObj ? tempObj[keyArr[i]] : undefined, }; } export const FormItemProps = { @@ -69,7 +76,8 @@ export const FormItemProps = { hasFeedback: PropTypes.bool, colon: PropTypes.bool, labelAlign: PropTypes.oneOf(['left', 'right']), - prop: PropTypes.string, + prop: PropTypes.oneOfType([Array, String, Number]), + name: PropTypes.oneOfType([Array, String, Number]), rules: PropTypes.oneOfType([Array, Object]), autoLink: PropTypes.bool, required: PropTypes.bool, @@ -94,6 +102,7 @@ export default { }; }, data() { + warning(hasProp(this, 'prop'), `\`prop\` is deprecated. Please use \`name\` instead.`); return { validateState: this.validateStatus, validateMessage: '', @@ -105,21 +114,29 @@ export default { }, computed: { + fieldName() { + return this.name || this.prop; + }, + namePath() { + return getNamePath(this.fieldName); + }, fieldId() { - return this.id || (this.FormContext.name && this.prop) - ? `${this.FormContext.name}_${this.prop}` - : undefined; + if (this.id) { + return this.id; + } else if (!this.namePath.length) { + return undefined; + } else { + const formName = this.FormContext.name; + const mergedId = this.namePath.join('_'); + return formName ? `${formName}_${mergedId}` : mergedId; + } }, fieldValue() { const model = this.FormContext.model; - if (!model || !this.prop) { + if (!model || !this.fieldName) { return; } - let path = this.prop; - if (path.indexOf(':') !== -1) { - path = path.replace(/:/g, '.'); - } - return getPropByPath(model, path, true).v; + return getPropByPath(model, this.namePath, true).v; }, isRequired() { let rules = this.getRules(); @@ -145,7 +162,7 @@ export default { provide('isFormItemChildren', true); }, mounted() { - if (this.prop) { + if (this.fieldName) { const { addField } = this.FormContext; addField && addField(this); this.initialValue = cloneDeep(this.fieldValue); @@ -157,10 +174,10 @@ export default { }, methods: { getNamePath() { - const { prop } = this.$props; + const { fieldName } = this; const { prefixName = [] } = this.FormContext; - return prop !== undefined ? [...prefixName, ...getNamePath(prop)] : []; + return fieldName !== undefined ? [...prefixName, ...this.namePath] : []; }, validateRules(options) { const { validateFirst = false, messageVariables } = this.$props; @@ -170,15 +187,17 @@ export default { let filteredRules = this.getRules(); if (triggerName) { filteredRules = filteredRules.filter(rule => { - const { validateTrigger } = rule; - if (!validateTrigger) { + const { trigger } = rule; + if (!trigger) { return true; } - const triggerList = toArray(validateTrigger); + const triggerList = toArray(trigger); return triggerList.includes(triggerName); }); } - + if (!filteredRules.length) { + return Promise.resolve(); + } const promise = validateRules( namePath, this.fieldValue, @@ -207,8 +226,8 @@ export default { const selfRules = this.rules; const requiredRule = this.required !== undefined ? { required: !!this.required, trigger: 'change' } : []; - const prop = getPropByPath(formRules, this.prop || ''); - formRules = formRules ? prop.o[this.prop || ''] || prop.v : []; + const prop = getPropByPath(formRules, this.namePath); + formRules = formRules ? prop.o[prop.k] || prop.v : []; return [].concat(selfRules || formRules || []).concat(requiredRule); }, getFilteredRule(trigger) { @@ -242,13 +261,9 @@ export default { resetField() { this.validateState = ''; this.validateMessage = ''; - let model = this.FormContext.model || {}; - let value = this.fieldValue; - let path = this.prop; - if (path.indexOf(':') !== -1) { - path = path.replace(/:/, '.'); - } - let prop = getPropByPath(model, path, true); + const model = this.FormContext.model || {}; + const value = this.fieldValue; + const prop = getPropByPath(model, this.namePath, true); this.validateDisabled = true; if (Array.isArray(value)) { prop.o[prop.k] = [].concat(this.initialValue); @@ -456,7 +471,7 @@ export default { const { autoLink } = getOptionProps(this); const children = getSlot(this); let firstChildren = children[0]; - if (this.prop && autoLink && isValidElement(firstChildren)) { + if (this.fieldName && autoLink && isValidElement(firstChildren)) { const originalEvents = getEvents(firstChildren); const originalBlur = originalEvents.onBlur; const originalChange = originalEvents.onChange; diff --git a/components/form-model/utils.js b/components/form-model/utils.js deleted file mode 100644 index 9754f17b0..000000000 --- a/components/form-model/utils.js +++ /dev/null @@ -1,143 +0,0 @@ -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 (typeof names === 'string') { - names = [names]; - } - 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 hasRules(validate) { - if (validate) { - return validate.some(item => { - return item.rules && item.rules.length; - }); - } - return false; -} - -export 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; -} - -export 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; -} - -// export async function finishOnAllFailed(rulePromises) { -// return Promise.all(rulePromises).then(errorsList => { -// const errors = [].concat(...errorsList); - -// return errors; -// }); -// } - -// export async function finishOnFirstFailed(rulePromises) { -// let count = 0; - -// return new Promise(resolve => { -// rulePromises.forEach(promise => { -// promise.then(errors => { -// if (errors.length) { -// resolve(errors); -// } - -// count += 1; -// if (count === rulePromises.length) { -// resolve([]); -// } -// }); -// }); -// }); -// } - -// export function toArray(value) { -// if (value === undefined || value === null) { -// return []; -// } - -// return Array.isArray(value) ? value : [value]; -// } - -// export function getNamePath(path) { -// return toArray(path); -// } diff --git a/package.json b/package.json index 0eb058461..521c9e43b 100644 --- a/package.json +++ b/package.json @@ -199,6 +199,7 @@ "omit.js": "^1.0.0", "raf": "^3.4.0", "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", "shallow-equal": "^1.0.0", "shallowequal": "^1.0.2", "vue-ref": "^2.0.0",