Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(form): blur trigger support #1051

Merged
merged 5 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion examples/form/demos/error-message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
@submit="onSubmit"
scrollToFirstError="smooth"
>
<t-form-item label="用户名" help="这是用户名字段帮助说明" name="account">
<!-- !!!注意:当 FormItem 的 label 属性为 Function 时,errorMessage 模板中的 ${name} 会被替换为 FormItem.name 属性值 -->
<t-form-item :label="renderAccountLabel" help="这是用户名字段帮助说明" name="account">
<t-input v-model="formData.account"></t-input>
</t-form-item>
<t-form-item label="个人简介" help="一句话介绍自己" name="description">
Expand Down Expand Up @@ -149,6 +150,9 @@ export default {
},

methods: {
renderAccountLabel() {
return '用户名';
},
onReset() {
this.$message.success('重置成功');
},
Expand Down
21 changes: 19 additions & 2 deletions examples/form/demos/validator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,26 @@ export default {
// FormItem.rules 优先级大于 Form.rules
rules: {
account: [
{
required: true,
message: '姓名必填',
type: 'error',
trigger: 'blur',
},
// trigger 默认为 'change'
{ required: true, message: '姓名必填', type: 'error' },
{ min: 2, message: '至少需要两个字符,一个中文等于两个字符', type: 'warning' },
{ max: 10, message: '姓名字符长度超出', type: 'warning' },
{
min: 2,
message: '至少需要两个字符,一个中文等于两个字符',
type: 'warning',
trigger: 'blur',
},
{
max: 10,
message: '姓名字符长度超出',
type: 'warning',
trigger: 'blur',
},
],
description: [
{
Expand Down
24 changes: 24 additions & 0 deletions src/form/const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { prefix } from '../config';
import { AllValidateResult, FormRule, ValidateResultType } from '../form/type';

export const FORM_ITEM_CLASS_PREFIX = `${prefix}-form-item__`;

Expand Down Expand Up @@ -45,3 +46,26 @@ export const FORM_CONTROL_COMPONENTS = [
'TTransfer',
'TSlider',
];

export type ErrorListType =
| {
result: false;
message: string;
type: 'error' | 'warning';
}
| ValidateResultType;
export type SuccessListType =
| {
result: true;
message: string;
type: 'success';
}
| ValidateResultType;

export interface AnalysisValidateResult {
successList?: SuccessListType[];
errorList?: ErrorListType[];
rules: FormRule[];
resultList: AllValidateResult[];
allowSetValue: boolean;
}
53 changes: 36 additions & 17 deletions src/form/form-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import {
FormItemValidateMessage,
} from './type';
import props from './form-item-props';
import { CLASS_NAMES, FORM_ITEM_CLASS_PREFIX } from './const';
import {
AnalysisValidateResult, CLASS_NAMES, ErrorListType, FORM_ITEM_CLASS_PREFIX, SuccessListType,
} from './const';
import Form from './form';
import { ClassName, TNodeReturnValue, Styles } from '../common';
import mixins from '../utils/mixins';
Expand Down Expand Up @@ -55,6 +57,12 @@ export default mixins(getConfigReceiverMixins<FormItemConstructor, FormConfig>('
form: { default: undefined },
},

provide(): { tFormItem: any } {
return {
tFormItem: this,
};
},

data() {
return {
// 校验不通过信息列表
Expand Down Expand Up @@ -243,11 +251,13 @@ export default mixins(getConfigReceiverMixins<FormItemConstructor, FormConfig>('
this.freeShowErrorMessage = source === 'submit-function' ? showErrorMessage : undefined;
this.resetValidating = true;
const {
errorList, resultList, successList, rules,
errorList, resultList, successList, rules, allowSetValue,
} = await this.analysisValidateResult(trigger);
this.errorList = errorList;
// 仅有自定义校验方法才会存在 successList
this.successList = successList;
if (allowSetValue) {
this.errorList = errorList;
// 仅有自定义校验方法才会存在 successList
this.successList = successList;
}
// 根据校验结果设置校验状态
if (rules.length) {
this.verifyStatus = errorList.length ? VALIDATE_STATUS.FAIL : VALIDATE_STATUS.SUCCESS;
Expand All @@ -272,33 +282,42 @@ export default mixins(getConfigReceiverMixins<FormItemConstructor, FormConfig>('
},

async analysisValidateResult(trigger: ValidateTriggerType) {
const result: AnalysisValidateResult = {
successList: [],
errorList: [],
rules: [],
resultList: [],
allowSetValue: false,
};
// 过滤不需要校验的规则
const rules = trigger === 'all' ? this.innerRules : this.innerRules.filter((item) => (item.trigger || 'change') === trigger);
result.rules = trigger === 'all' ? this.innerRules : this.innerRules.filter((item) => (item.trigger || 'change') === trigger);
if (this.innerRules.length && !result.rules.length) {
return result;
}
// 校验结果,包含正确的校验信息
const resultList = await validate(this.value, rules);
const errorList = resultList
result.allowSetValue = true;
result.resultList = await validate(this.value, result.rules);
result.errorList = result.resultList
.filter((item) => item.result !== true)
.map((item) => {
.map((item: ErrorListType) => {
Object.keys(item).forEach((key) => {
if (typeof item.message === 'undefined' && this.errorMessages[key]) {
const compiled = lodashTemplate(this.errorMessages[key]);
const name = typeof this.label === 'string' ? this.label : this.name;
// eslint-disable-next-line no-param-reassign
item.message = compiled({
name: this.label,
name,
validate: item[key],
});
}
});
return item;
});
// 仅有自定义校验方法才会存在 successList
const successList = resultList.filter((item) => item.result === true && item.message && item.type === 'success');
return {
resultList,
errorList,
successList,
rules,
};
result.successList = result.resultList.filter(
(item) => item.result === true && item.message && item.type === 'success',
) as SuccessListType[];
return result;
},

getLabelContent(): TNodeReturnValue {
Expand Down
23 changes: 15 additions & 8 deletions src/form/form-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import isURL from 'validator/lib/isURL';
import isNumber from 'lodash/isNumber';
import { getCharacterLength } from '../utils/helper';
import {
CustomValidator, FormRule, ValueType, AllValidateResult,
CustomValidator,
FormRule,
ValueType,
AllValidateResult,
CustomValidateResolveType,
ValidateResultType,
} from './type';

// `{} / [] / '' / undefined / null` 等内容被认为是空; 0 和 false 被认为是正常数据,部分数据的值就是 0 或者 false
Expand Down Expand Up @@ -39,7 +44,7 @@ const VALIDATE_MAP = {
max: (val: ValueType, num: number): boolean => compareValue(val, num, true),
min: (val: ValueType, num: number): boolean => compareValue(val, num, false),
len: (val: ValueType, num: number): boolean => getCharacterLength(val) === num,
number: (val: ValueType): boolean => !Number.isNaN(val),
number: (val: ValueType): boolean => isNumber(val),
enum: (val: ValueType, strs: Array<string>): boolean => strs.includes(val),
idcard: (val: ValueType): boolean => /^(\d{18,18}|\d{15,15}|\d{17,17}x)$/i.test(val),
telnumber: (val: ValueType): boolean => /^1[3-9]\d{9}$/.test(val),
Expand All @@ -48,28 +53,30 @@ const VALIDATE_MAP = {
validator: (val: ValueType, validate: CustomValidator): ReturnType<CustomValidator> => validate(val),
};

export type ValidateFuncType = typeof VALIDATE_MAP[keyof typeof VALIDATE_MAP];

/**
* 校验某一条数据的某一条规则,一种校验规则不满足则不再进行校验。
* @param value 值
* @param rule 校验规则
* @returns 两种校验结果,一种是内置校验规则的校验结果哦,二种是自定义校验规则(validator)的校验结果
*/
export async function validateOneRule(value: ValueType, rule: FormRule): Promise<AllValidateResult> {
let validateResult: AllValidateResult = { result: true };
let validateResult: CustomValidateResolveType | ValidateResultType = { result: true };
const keys = Object.keys(rule);
let vOptions = {};
let vValidateFun;
let vOptions;
let vValidateFun: ValidateFuncType;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
// 非必填选项,值为空,非自定义规则:无需校验,直接返回 true
if (!rule.required && isValueEmpty(value) && !rule.validator) {
return validateResult;
}
const validateRule = VALIDATE_MAP[key];
const validateRule: ValidateFuncType = VALIDATE_MAP[key];
// 找到一个校验规则,则无需再找,因为参数只允许对一个规则进行校验
if (validateRule && rule[key]) {
// rule 值为 true 则表示没有校验参数,只是对值进行默认规则校验
vOptions = rule[key] === true ? {} : rule[key];
vOptions = rule[key] === true ? undefined : rule[key];
vValidateFun = validateRule;
break;
}
Expand All @@ -80,7 +87,7 @@ export async function validateOneRule(value: ValueType, rule: FormRule): Promise
if (typeof validateResult === 'boolean') {
return { ...rule, result: validateResult };
}
// 校验结果为 CustomValidateResolveType,只有自定义校验规则会存在这种情况
// 校验结果为 CustomValidateObj,只有自定义校验规则会存在这种情况
if (typeof validateResult === 'object') {
return validateResult;
}
Expand Down
3 changes: 3 additions & 0 deletions src/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { emitEvent } from '../utils/event';
import { prefix } from '../config';
import props from './props';
import { renderTNodeJSX } from '../utils/render-tnode';
import FormItem from '../form/form-item';

const name = `${prefix}-input`;
const INPUT_WRAP_CLASS = `${prefix}-input__wrap`;
Expand All @@ -29,6 +30,7 @@ function getValidAttrs(obj: object): object {

interface InputInstance extends Vue {
composing: boolean;
tFormItem: InstanceType<typeof FormItem>;
}

export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input')).extend({
Expand Down Expand Up @@ -232,6 +234,7 @@ export default mixins(getConfigReceiverMixins<InputInstance, InputConfig>('input
this.inputValue = this.format(this.value);
}
this.focused = false;
this.tFormItem?.validate('blur');
emitEvent<Parameters<TdInputProps['onBlur']>>(this, 'blur', this.value, { e });
},
compositionstartHandler(e: CompositionEvent) {
Expand Down
14 changes: 12 additions & 2 deletions src/textarea/textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { VueConstructor } from 'vue';
import isFunction from 'lodash/isFunction';
import { prefix } from '../config';
import CLASSNAMES from '../utils/classnames';
Expand All @@ -22,11 +22,20 @@ function getValidAttrs(obj: object): object {
});
return newObj;
}
export default Vue.extend({
export interface Textarea extends Vue {
tFormItem: {
validate(trigger: string): Promise<any>
};
}

export default (Vue as VueConstructor<Textarea>).extend({
name: 'TTextarea',
props: {
...props,
},
inject: {
tFormItem: { default: undefined },
},
data() {
return {
formDisabled: undefined,
Expand Down Expand Up @@ -151,6 +160,7 @@ export default Vue.extend({
},
emitBlur(e: FocusEvent) {
this.focused = false;
this.tFormItem?.validate('blur');
uyarn marked this conversation as resolved.
Show resolved Hide resolved
this.emitEvent('blur', this.value, { e });
},
},
Expand Down