Are you an LLM? You can read better optimized documentation at /front/component/ma-form/examples/dynamic-validation.md for this page in Markdown format
Dynamic Validation β
Demonstrates MaForm's dynamic validation capabilities, including custom synchronous validation, asynchronous validation, linked validation, and complex validation rules.
Features β
- Multi-level Validation: Supports Element Plus native rules, custom synchronous validation, and asynchronous validation
- Linked Validation: Interdependent validation logic between fields
- Real-time Validation: Instant validation feedback during input
- Asynchronous Validation: Supports server-side validation (e.g., uniqueness checks)
- Custom Error Messages: Flexible error message customization
Validation Levels β
1. Element Plus Native Validation Rules β
typescript
{
label: 'Username',
prop: 'username',
render: 'input',
itemProps: {
rules: [
{ required: true, message: 'Please enter username', trigger: 'blur' },
{ min: 3, max: 15, message: 'Length should be 3 to 15 characters', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: 'Only letters, numbers and underscores allowed', trigger: 'blur' }
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
2. Custom Synchronous Validation β
typescript
{
label: 'Confirm Password',
prop: 'confirmPassword',
render: 'input',
renderProps: {
type: 'password',
showPassword: true
},
customValidator: (rule, value, callback) => {
if (!value) {
callback(new Error('Please confirm password'))
} else if (value !== formData.password) {
callback(new Error('Passwords do not match'))
} else {
callback()
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3. Asynchronous Validation β
typescript
{
label: 'Email Address',
prop: 'email',
render: 'input',
renderProps: {
type: 'email'
},
asyncValidator: async (rule, value) => {
if (!value) {
throw new Error('Please enter email address')
}
// Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(value)) {
throw new Error('Please enter a valid email address')
}
// Asynchronous email existence check
try {
const exists = await checkEmailExists(value)
if (exists) {
throw new Error('This email is already registered')
}
} catch (error) {
if (error.message.includes('already registered')) {
throw error
}
throw new Error('Email verification failed, please try again later')
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Complex Validation Scenarios β
1. Linked Validation β
typescript
const linkedValidationFields = [
{
label: 'Start Date',
prop: 'startDate',
render: 'datePicker',
renderProps: {
type: 'date',
placeholder: 'Select start date'
},
customValidator: (rule, value, callback) => {
if (!value) {
callback(new Error('Please select start date'))
return
}
const endDate = formData.endDate
if (endDate && new Date(value) >= new Date(endDate)) {
callback(new Error('Start date must be earlier than end date'))
} else {
callback()
// Trigger end date revalidation
nextTick(() => {
if (endDate) {
formRef.value?.validateField('endDate')
}
})
}
}
},
{
label: 'End Date',
prop: 'endDate',
render: 'datePicker',
renderProps: {
type: 'date',
placeholder: 'Select end date'
},
customValidator: (rule, value, callback) => {
if (!value) {
callback(new Error('Please select end date'))
return
}
const startDate = formData.startDate
if (startDate && new Date(value) <= new Date(startDate)) {
callback(new Error('End date must be later than start date'))
} else {
callback()
// Trigger start date revalidation
nextTick(() => {
if (startDate) {
formRef.value?.validateField('startDate')
}
})
}
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2. Conditional Validation β
typescript
{
label: 'Phone Number',
prop: 'phoneNumber',
render: 'input',
customValidator: (rule, value, callback) => {
// Determine if required based on user type
const userType = formData.userType
const isRequired = userType === 'individual'
if (isRequired && !value) {
callback(new Error('Individual users must provide phone number'))
return
}
if (value && !/^1[3-9]\d{9}$/.test(value)) {
callback(new Error('Please enter a valid phone number'))
return
}
callback()
},
dependencies: ['userType']
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3. Business Logic Validation β
typescript
{
label: 'Product Price',
prop: 'price',
render: 'inputNumber',
renderProps: {
min: 0,
precision: 2,
controlsPosition: 'right'
},
customValidator: (rule, value, callback) => {
if (value === null || value === undefined) {
callback(new Error('Please enter product price'))
return
}
if (value <= 0) {
callback(new Error('Product price must be greater than 0'))
return
}
// Business rule: VIP product price cannot be lower than 100
const isVipProduct = formData.isVipProduct
if (isVipProduct && value < 100) {
callback(new Error('VIP product price cannot be lower than 100'))
return
}
// Business rule: Promotional product price cannot exceed 1000
const isPromotional = formData.isPromotional
if (isPromotional && value > 1000) {
callback(new Error('Promotional product price cannot exceed 1000'))
return
}
callback()
},
dependencies: ['isVipProduct', 'isPromotional']
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Asynchronous Validation Best Practices β
1. Debounce Handling β
typescript
// Create debounce function
const debounce = (func, wait) => {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
// Debounced async validation
const debouncedUsernameCheck = debounce(async (username) => {
return await checkUsernameExists(username)
}, 500)
{
label: 'Username',
prop: 'username',
render: 'input',
asyncValidator: async (rule, value) => {
if (!value) {
throw new Error('Please enter username')
}
const exists = await debouncedUsernameCheck(value)
if (exists) {
throw new Error('Username already exists')
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2. Loading State Indicator β
typescript
{
label: 'Username',
prop: 'username',
render: 'input',
renderProps: {
loading: false // Dynamically updated via updateItem
},
asyncValidator: async (rule, value) => {
if (!value) {
throw new Error('Please enter username')
}
// Show loading state
formRef.value?.updateItem('username', {
renderProps: { loading: true }
})
try {
const exists = await checkUsernameExists(value)
if (exists) {
throw new Error('Username already exists')
}
} finally {
// Hide loading state
formRef.value?.updateItem('username', {
renderProps: { loading: false }
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
3. Error Handling β
typescript
{
label: 'Email',
prop: 'email',
render: 'input',
asyncValidator: async (rule, value) => {
try {
const response = await fetch(`/api/check-email?email=${value}`)
if (!response.ok) {
// HTTP error handling
if (response.status === 429) {
throw new Error('Too many requests, please try again later')
}
throw new Error('Verification service temporarily unavailable')
}
const result = await response.json()
if (result.exists) {
throw new Error('This email is already registered')
}
} catch (error) {
if (error.name === 'TypeError') {
// Network error
throw new Error('Network connection failed, please check your settings')
}
throw error
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Validation Timing Control β
1. Trigger Configuration β
typescript
{
label: 'Real-time Validation Field',
prop: 'realtime',
render: 'input',
itemProps: {
rules: [
{ required: true, message: 'This field is required', trigger: 'change' } // Validate during input
]
}
}
{
label: 'Blur Validation Field',
prop: 'onblur',
render: 'input',
itemProps: {
rules: [
{ required: true, message: 'This field is required', trigger: 'blur' } // Validate on blur
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2. Manual Validation Control β
typescript
// Validate specific field
const validateSingleField = async (prop) => {
try {
const isValid = await formRef.value?.validateField(prop)
console.log(`Field ${prop} validation result:`, isValid)
} catch (error) {
console.error(`Field ${prop} validation failed:`, error)
}
}
// Validate entire form
const validateForm = async () => {
try {
const isValid = await formRef.value?.validate()
if (isValid) {
console.log('Form validation passed')
}
} catch (error) {
console.error('Form validation failed:', error)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21