useForm.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import { reactive, watch, nextTick, unref, shallowRef, toRaw, ref } from 'vue';
  3. import cloneDeep from 'lodash-es/cloneDeep';
  4. import intersection from 'lodash-es/intersection';
  5. import isEqual from 'lodash-es/isEqual';
  6. import debounce from 'lodash-es/debounce';
  7. import omit from 'lodash-es/omit';
  8. import { validateRules } from './utils/validateUtil';
  9. import { defaultValidateMessages } from './utils/messages';
  10. import { allPromiseFinish } from './utils/asyncUtil';
  11. function isRequired(rules) {
  12. let isRequired = false;
  13. if (rules && rules.length) {
  14. rules.every(rule => {
  15. if (rule.required) {
  16. isRequired = true;
  17. return false;
  18. }
  19. return true;
  20. });
  21. }
  22. return isRequired;
  23. }
  24. function toArray(value) {
  25. if (value === undefined || value === null) {
  26. return [];
  27. }
  28. return Array.isArray(value) ? value : [value];
  29. }
  30. function getPropByPath(obj, path, strict) {
  31. let tempObj = obj;
  32. path = path.replace(/\[(\w+)\]/g, '.$1');
  33. path = path.replace(/^\./, '');
  34. const keyArr = path.split('.');
  35. let i = 0;
  36. for (let len = keyArr.length; i < len - 1; ++i) {
  37. if (!tempObj && !strict) break;
  38. const key = keyArr[i];
  39. if (key in tempObj) {
  40. tempObj = tempObj[key];
  41. } else {
  42. if (strict) {
  43. throw new Error('please transfer a valid name path to validate!');
  44. }
  45. break;
  46. }
  47. }
  48. return {
  49. o: tempObj,
  50. k: keyArr[i],
  51. v: tempObj ? tempObj[keyArr[i]] : null,
  52. isValid: tempObj && keyArr[i] in tempObj
  53. };
  54. }
  55. function useForm(modelRef) {
  56. let rulesRef = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ref({});
  57. let options = arguments.length > 2 ? arguments[2] : undefined;
  58. const initialModel = cloneDeep(unref(modelRef));
  59. const validateInfos = reactive({});
  60. const rulesKeys = shallowRef([]);
  61. const resetFields = newValues => {
  62. _extends(unref(modelRef), _extends(_extends({}, cloneDeep(initialModel)), newValues));
  63. nextTick(() => {
  64. Object.keys(validateInfos).forEach(key => {
  65. validateInfos[key] = {
  66. autoLink: false,
  67. required: isRequired(unref(rulesRef)[key])
  68. };
  69. });
  70. });
  71. };
  72. const filterRules = function () {
  73. let rules = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  74. let trigger = arguments.length > 1 ? arguments[1] : undefined;
  75. if (!trigger.length) {
  76. return rules;
  77. } else {
  78. return rules.filter(rule => {
  79. const triggerList = toArray(rule.trigger || 'change');
  80. return intersection(triggerList, trigger).length;
  81. });
  82. }
  83. };
  84. let lastValidatePromise = null;
  85. const validateFields = function (names) {
  86. let option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  87. let strict = arguments.length > 2 ? arguments[2] : undefined;
  88. // Collect result in promise list
  89. const promiseList = [];
  90. const values = {};
  91. for (let i = 0; i < names.length; i++) {
  92. const name = names[i];
  93. const prop = getPropByPath(unref(modelRef), name, strict);
  94. if (!prop.isValid) continue;
  95. values[name] = prop.v;
  96. const rules = filterRules(unref(rulesRef)[name], toArray(option && option.trigger));
  97. if (rules.length) {
  98. promiseList.push(validateField(name, prop.v, rules, option || {}).then(() => ({
  99. name,
  100. errors: [],
  101. warnings: []
  102. })).catch(ruleErrors => {
  103. const mergedErrors = [];
  104. const mergedWarnings = [];
  105. ruleErrors.forEach(_ref => {
  106. let {
  107. rule: {
  108. warningOnly
  109. },
  110. errors
  111. } = _ref;
  112. if (warningOnly) {
  113. mergedWarnings.push(...errors);
  114. } else {
  115. mergedErrors.push(...errors);
  116. }
  117. });
  118. if (mergedErrors.length) {
  119. return Promise.reject({
  120. name,
  121. errors: mergedErrors,
  122. warnings: mergedWarnings
  123. });
  124. }
  125. return {
  126. name,
  127. errors: mergedErrors,
  128. warnings: mergedWarnings
  129. };
  130. }));
  131. }
  132. }
  133. const summaryPromise = allPromiseFinish(promiseList);
  134. lastValidatePromise = summaryPromise;
  135. const returnPromise = summaryPromise.then(() => {
  136. if (lastValidatePromise === summaryPromise) {
  137. return Promise.resolve(values);
  138. }
  139. return Promise.reject([]);
  140. }).catch(results => {
  141. const errorList = results.filter(result => result && result.errors.length);
  142. return errorList.length ? Promise.reject({
  143. values,
  144. errorFields: errorList,
  145. outOfDate: lastValidatePromise !== summaryPromise
  146. }) : Promise.resolve(values);
  147. });
  148. // Do not throw in console
  149. returnPromise.catch(e => e);
  150. return returnPromise;
  151. };
  152. const validateField = function (name, value, rules) {
  153. let option = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  154. const promise = validateRules([name], value, rules, _extends({
  155. validateMessages: defaultValidateMessages
  156. }, option), !!option.validateFirst);
  157. if (!validateInfos[name]) {
  158. return promise.catch(e => e);
  159. }
  160. validateInfos[name].validateStatus = 'validating';
  161. promise.catch(e => e).then(function () {
  162. let results = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  163. var _a;
  164. if (validateInfos[name].validateStatus === 'validating') {
  165. const res = results.filter(result => result && result.errors.length);
  166. validateInfos[name].validateStatus = res.length ? 'error' : 'success';
  167. validateInfos[name].help = res.length ? res.map(r => r.errors) : null;
  168. (_a = options === null || options === void 0 ? void 0 : options.onValidate) === null || _a === void 0 ? void 0 : _a.call(options, name, !res.length, res.length ? toRaw(validateInfos[name].help[0]) : null);
  169. }
  170. });
  171. return promise;
  172. };
  173. const validate = (names, option) => {
  174. let keys = [];
  175. let strict = true;
  176. if (!names) {
  177. strict = false;
  178. keys = rulesKeys.value;
  179. } else if (Array.isArray(names)) {
  180. keys = names;
  181. } else {
  182. keys = [names];
  183. }
  184. const promises = validateFields(keys, option || {}, strict);
  185. // Do not throw in console
  186. promises.catch(e => e);
  187. return promises;
  188. };
  189. const clearValidate = names => {
  190. let keys = [];
  191. if (!names) {
  192. keys = rulesKeys.value;
  193. } else if (Array.isArray(names)) {
  194. keys = names;
  195. } else {
  196. keys = [names];
  197. }
  198. keys.forEach(key => {
  199. validateInfos[key] && _extends(validateInfos[key], {
  200. validateStatus: '',
  201. help: null
  202. });
  203. });
  204. };
  205. const mergeValidateInfo = items => {
  206. const info = {
  207. autoLink: false
  208. };
  209. const help = [];
  210. const infos = Array.isArray(items) ? items : [items];
  211. for (let i = 0; i < infos.length; i++) {
  212. const arg = infos[i];
  213. if ((arg === null || arg === void 0 ? void 0 : arg.validateStatus) === 'error') {
  214. info.validateStatus = 'error';
  215. arg.help && help.push(arg.help);
  216. }
  217. info.required = info.required || (arg === null || arg === void 0 ? void 0 : arg.required);
  218. }
  219. info.help = help;
  220. return info;
  221. };
  222. let oldModel = initialModel;
  223. let isFirstTime = true;
  224. const modelFn = model => {
  225. const names = [];
  226. rulesKeys.value.forEach(key => {
  227. const prop = getPropByPath(model, key, false);
  228. const oldProp = getPropByPath(oldModel, key, false);
  229. const isFirstValidation = isFirstTime && (options === null || options === void 0 ? void 0 : options.immediate) && prop.isValid;
  230. if (isFirstValidation || !isEqual(prop.v, oldProp.v)) {
  231. names.push(key);
  232. }
  233. });
  234. validate(names, {
  235. trigger: 'change'
  236. });
  237. isFirstTime = false;
  238. oldModel = cloneDeep(toRaw(model));
  239. };
  240. const debounceOptions = options === null || options === void 0 ? void 0 : options.debounce;
  241. let first = true;
  242. watch(rulesRef, () => {
  243. rulesKeys.value = rulesRef ? Object.keys(unref(rulesRef)) : [];
  244. if (!first && options && options.validateOnRuleChange) {
  245. validate();
  246. }
  247. first = false;
  248. }, {
  249. deep: true,
  250. immediate: true
  251. });
  252. watch(rulesKeys, () => {
  253. const newValidateInfos = {};
  254. rulesKeys.value.forEach(key => {
  255. newValidateInfos[key] = _extends({}, validateInfos[key], {
  256. autoLink: false,
  257. required: isRequired(unref(rulesRef)[key])
  258. });
  259. delete validateInfos[key];
  260. });
  261. for (const key in validateInfos) {
  262. if (Object.prototype.hasOwnProperty.call(validateInfos, key)) {
  263. delete validateInfos[key];
  264. }
  265. }
  266. _extends(validateInfos, newValidateInfos);
  267. }, {
  268. immediate: true
  269. });
  270. watch(modelRef, debounceOptions && debounceOptions.wait ? debounce(modelFn, debounceOptions.wait, omit(debounceOptions, ['wait'])) : modelFn, {
  271. immediate: options && !!options.immediate,
  272. deep: true
  273. });
  274. return {
  275. modelRef,
  276. rulesRef,
  277. initialModel,
  278. validateInfos,
  279. resetFields,
  280. validate,
  281. validateField,
  282. mergeValidateInfo,
  283. clearValidate
  284. };
  285. }
  286. export default useForm;