Form.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import { createVNode as _createVNode } from "vue";
  4. import { defineComponent, computed, watch, ref } from 'vue';
  5. import PropTypes from '../_util/vue-types';
  6. import classNames from '../_util/classNames';
  7. import warning from '../_util/warning';
  8. import FormItem from './FormItem';
  9. import { getNamePath, containsNamePath, cloneByNamePathList } from './utils/valueUtil';
  10. import { defaultValidateMessages } from './utils/messages';
  11. import { allPromiseFinish } from './utils/asyncUtil';
  12. import { toArray } from './utils/typeUtil';
  13. import isEqual from 'lodash-es/isEqual';
  14. import scrollIntoView from 'scroll-into-view-if-needed';
  15. import initDefaultProps from '../_util/props-util/initDefaultProps';
  16. import { anyType, booleanType, functionType, objectType, someType, stringType, tuple } from '../_util/type';
  17. import useConfigInject from '../config-provider/hooks/useConfigInject';
  18. import { useProvideForm } from './context';
  19. import useForm from './useForm';
  20. import { useInjectGlobalForm } from '../config-provider/context';
  21. import useStyle from './style';
  22. import { useProviderSize } from '../config-provider/SizeContext';
  23. import { useProviderDisabled } from '../config-provider/DisabledContext';
  24. export const formProps = () => ({
  25. layout: PropTypes.oneOf(tuple('horizontal', 'inline', 'vertical')),
  26. labelCol: objectType(),
  27. wrapperCol: objectType(),
  28. colon: booleanType(),
  29. labelAlign: stringType(),
  30. labelWrap: booleanType(),
  31. prefixCls: String,
  32. requiredMark: someType([String, Boolean]),
  33. /** @deprecated Will warning in future branch. Pls use `requiredMark` instead. */
  34. hideRequiredMark: booleanType(),
  35. model: PropTypes.object,
  36. rules: objectType(),
  37. validateMessages: objectType(),
  38. validateOnRuleChange: booleanType(),
  39. // 提交失败自动滚动到第一个错误字段
  40. scrollToFirstError: anyType(),
  41. onSubmit: functionType(),
  42. name: String,
  43. validateTrigger: someType([String, Array]),
  44. size: stringType(),
  45. disabled: booleanType(),
  46. onValuesChange: functionType(),
  47. onFieldsChange: functionType(),
  48. onFinish: functionType(),
  49. onFinishFailed: functionType(),
  50. onValidate: functionType()
  51. });
  52. function isEqualName(name1, name2) {
  53. return isEqual(toArray(name1), toArray(name2));
  54. }
  55. const Form = defineComponent({
  56. compatConfig: {
  57. MODE: 3
  58. },
  59. name: 'AForm',
  60. inheritAttrs: false,
  61. props: initDefaultProps(formProps(), {
  62. layout: 'horizontal',
  63. hideRequiredMark: false,
  64. colon: true
  65. }),
  66. Item: FormItem,
  67. useForm,
  68. // emits: ['finishFailed', 'submit', 'finish', 'validate'],
  69. setup(props, _ref) {
  70. let {
  71. emit,
  72. slots,
  73. expose,
  74. attrs
  75. } = _ref;
  76. const {
  77. prefixCls,
  78. direction,
  79. form: contextForm,
  80. size,
  81. disabled
  82. } = useConfigInject('form', props);
  83. const requiredMark = computed(() => props.requiredMark === '' || props.requiredMark);
  84. const mergedRequiredMark = computed(() => {
  85. var _a;
  86. if (requiredMark.value !== undefined) {
  87. return requiredMark.value;
  88. }
  89. if (contextForm && ((_a = contextForm.value) === null || _a === void 0 ? void 0 : _a.requiredMark) !== undefined) {
  90. return contextForm.value.requiredMark;
  91. }
  92. if (props.hideRequiredMark) {
  93. return false;
  94. }
  95. return true;
  96. });
  97. useProviderSize(size);
  98. useProviderDisabled(disabled);
  99. const mergedColon = computed(() => {
  100. var _a, _b;
  101. return (_a = props.colon) !== null && _a !== void 0 ? _a : (_b = contextForm.value) === null || _b === void 0 ? void 0 : _b.colon;
  102. });
  103. const {
  104. validateMessages: globalValidateMessages
  105. } = useInjectGlobalForm();
  106. const validateMessages = computed(() => {
  107. return _extends(_extends(_extends({}, defaultValidateMessages), globalValidateMessages.value), props.validateMessages);
  108. });
  109. // Style
  110. const [wrapSSR, hashId] = useStyle(prefixCls);
  111. const formClassName = computed(() => classNames(prefixCls.value, {
  112. [`${prefixCls.value}-${props.layout}`]: true,
  113. [`${prefixCls.value}-hide-required-mark`]: mergedRequiredMark.value === false,
  114. [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
  115. [`${prefixCls.value}-${size.value}`]: size.value
  116. }, hashId.value));
  117. const lastValidatePromise = ref();
  118. const fields = {};
  119. const addField = (eventKey, field) => {
  120. fields[eventKey] = field;
  121. };
  122. const removeField = eventKey => {
  123. delete fields[eventKey];
  124. };
  125. const getFieldsByNameList = nameList => {
  126. const provideNameList = !!nameList;
  127. const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
  128. if (!provideNameList) {
  129. return Object.values(fields);
  130. } else {
  131. return Object.values(fields).filter(field => namePathList.findIndex(namePath => isEqualName(namePath, field.fieldName.value)) > -1);
  132. }
  133. };
  134. const resetFields = name => {
  135. if (!props.model) {
  136. warning(false, 'Form', 'model is required for resetFields to work.');
  137. return;
  138. }
  139. getFieldsByNameList(name).forEach(field => {
  140. field.resetField();
  141. });
  142. };
  143. const clearValidate = name => {
  144. getFieldsByNameList(name).forEach(field => {
  145. field.clearValidate();
  146. });
  147. };
  148. const handleFinishFailed = errorInfo => {
  149. const {
  150. scrollToFirstError
  151. } = props;
  152. emit('finishFailed', errorInfo);
  153. if (scrollToFirstError && errorInfo.errorFields.length) {
  154. let scrollToFieldOptions = {};
  155. if (typeof scrollToFirstError === 'object') {
  156. scrollToFieldOptions = scrollToFirstError;
  157. }
  158. scrollToField(errorInfo.errorFields[0].name, scrollToFieldOptions);
  159. }
  160. };
  161. const validate = function () {
  162. return validateField(...arguments);
  163. };
  164. const scrollToField = function (name) {
  165. let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  166. const fields = getFieldsByNameList(name ? [name] : undefined);
  167. if (fields.length) {
  168. const fieldId = fields[0].fieldId.value;
  169. const node = fieldId ? document.getElementById(fieldId) : null;
  170. if (node) {
  171. scrollIntoView(node, _extends({
  172. scrollMode: 'if-needed',
  173. block: 'nearest'
  174. }, options));
  175. }
  176. }
  177. };
  178. // eslint-disable-next-line no-unused-vars
  179. const getFieldsValue = function () {
  180. let nameList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  181. if (nameList === true) {
  182. const allNameList = [];
  183. Object.values(fields).forEach(_ref2 => {
  184. let {
  185. namePath
  186. } = _ref2;
  187. allNameList.push(namePath.value);
  188. });
  189. return cloneByNamePathList(props.model, allNameList);
  190. } else {
  191. return cloneByNamePathList(props.model, nameList);
  192. }
  193. };
  194. const validateFields = (nameList, options) => {
  195. warning(!(nameList instanceof Function), 'Form', 'validateFields/validateField/validate not support callback, please use promise instead');
  196. if (!props.model) {
  197. warning(false, 'Form', 'model is required for validateFields to work.');
  198. return Promise.reject('Form `model` is required for validateFields to work.');
  199. }
  200. const provideNameList = !!nameList;
  201. const namePathList = provideNameList ? toArray(nameList).map(getNamePath) : [];
  202. // Collect result in promise list
  203. const promiseList = [];
  204. Object.values(fields).forEach(field => {
  205. var _a;
  206. // Add field if not provide `nameList`
  207. if (!provideNameList) {
  208. namePathList.push(field.namePath.value);
  209. }
  210. // Skip if without rule
  211. if (!((_a = field.rules) === null || _a === void 0 ? void 0 : _a.value.length)) {
  212. return;
  213. }
  214. const fieldNamePath = field.namePath.value;
  215. // Add field validate rule in to promise list
  216. if (!provideNameList || containsNamePath(namePathList, fieldNamePath)) {
  217. const promise = field.validateRules(_extends({
  218. validateMessages: validateMessages.value
  219. }, options));
  220. // Wrap promise with field
  221. promiseList.push(promise.then(() => ({
  222. name: fieldNamePath,
  223. errors: [],
  224. warnings: []
  225. })).catch(ruleErrors => {
  226. const mergedErrors = [];
  227. const mergedWarnings = [];
  228. ruleErrors.forEach(_ref3 => {
  229. let {
  230. rule: {
  231. warningOnly
  232. },
  233. errors
  234. } = _ref3;
  235. if (warningOnly) {
  236. mergedWarnings.push(...errors);
  237. } else {
  238. mergedErrors.push(...errors);
  239. }
  240. });
  241. if (mergedErrors.length) {
  242. return Promise.reject({
  243. name: fieldNamePath,
  244. errors: mergedErrors,
  245. warnings: mergedWarnings
  246. });
  247. }
  248. return {
  249. name: fieldNamePath,
  250. errors: mergedErrors,
  251. warnings: mergedWarnings
  252. };
  253. }));
  254. }
  255. });
  256. const summaryPromise = allPromiseFinish(promiseList);
  257. lastValidatePromise.value = summaryPromise;
  258. const returnPromise = summaryPromise.then(() => {
  259. if (lastValidatePromise.value === summaryPromise) {
  260. return Promise.resolve(getFieldsValue(namePathList));
  261. }
  262. return Promise.reject([]);
  263. }).catch(results => {
  264. const errorList = results.filter(result => result && result.errors.length);
  265. return Promise.reject({
  266. values: getFieldsValue(namePathList),
  267. errorFields: errorList,
  268. outOfDate: lastValidatePromise.value !== summaryPromise
  269. });
  270. });
  271. // Do not throw in console
  272. returnPromise.catch(e => e);
  273. return returnPromise;
  274. };
  275. const validateField = function () {
  276. return validateFields(...arguments);
  277. };
  278. const handleSubmit = e => {
  279. e.preventDefault();
  280. e.stopPropagation();
  281. emit('submit', e);
  282. if (props.model) {
  283. const res = validateFields();
  284. res.then(values => {
  285. emit('finish', values);
  286. }).catch(errors => {
  287. handleFinishFailed(errors);
  288. });
  289. }
  290. };
  291. expose({
  292. resetFields,
  293. clearValidate,
  294. validateFields,
  295. getFieldsValue,
  296. validate,
  297. scrollToField
  298. });
  299. useProvideForm({
  300. model: computed(() => props.model),
  301. name: computed(() => props.name),
  302. labelAlign: computed(() => props.labelAlign),
  303. labelCol: computed(() => props.labelCol),
  304. labelWrap: computed(() => props.labelWrap),
  305. wrapperCol: computed(() => props.wrapperCol),
  306. vertical: computed(() => props.layout === 'vertical'),
  307. colon: mergedColon,
  308. requiredMark: mergedRequiredMark,
  309. validateTrigger: computed(() => props.validateTrigger),
  310. rules: computed(() => props.rules),
  311. addField,
  312. removeField,
  313. onValidate: (name, status, errors) => {
  314. emit('validate', name, status, errors);
  315. },
  316. validateMessages
  317. });
  318. watch(() => props.rules, () => {
  319. if (props.validateOnRuleChange) {
  320. validateFields();
  321. }
  322. });
  323. return () => {
  324. var _a;
  325. return wrapSSR(_createVNode("form", _objectSpread(_objectSpread({}, attrs), {}, {
  326. "onSubmit": handleSubmit,
  327. "class": [formClassName.value, attrs.class]
  328. }), [(_a = slots.default) === null || _a === void 0 ? void 0 : _a.call(slots)]));
  329. };
  330. }
  331. });
  332. export default Form;