Skip to content

Commit 83e3b6a

Browse files
committed
update validate-directive
1 parent 1ac74f8 commit 83e3b6a

File tree

8 files changed

+3373
-2747
lines changed

8 files changed

+3373
-2747
lines changed

pnpm-lock.yaml

+3,271-2,666
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.vue

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
<script setup lang="ts">
2-
import { RouterLink, RouterView } from 'vue-router'
32
import HelloWorld from './components/HelloWorld.vue';
43
import HelloWorld2 from './components/HelloWorld2.vue'
54
import HelloWorld3 from './components/HelloWorld3.vue'
5+
import { useErrors } from "@/plugins/store";
6+
7+
import { ref } from 'vue';
8+
9+
const { errors } = useErrors()
10+
const test = ref("")
11+
12+
const checkError = () => {
13+
console.log(errors.value)
14+
}
615
</script>
716

817
<template>
918
<header>
1019
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
1120

1221
<div class="wrapper">
13-
<!-- <HelloWorld msg="Form 1" /> -->
14-
<!-- <HelloWorld2 msg="Form 2" /> -->
22+
<HelloWorld msg="Form 1" />
23+
<HelloWorld2 msg="Form 2" />
1524
<HelloWorld3 msg="Form 3" />
16-
17-
<nav ref="nav">
18-
<RouterLink to="/">Home</RouterLink>
19-
<RouterLink to="/about">About</RouterLink>
20-
</nav>
25+
2126
</div>
2227
</header>
23-
24-
<RouterView />
2528
</template>
2629

2730
<style scoped>

src/components/HelloWorld.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default {
1515
const email = ref("")
1616
1717
const checkError = () => {
18-
console.log(errors.value)
18+
1919
}
2020
2121
return {

src/components/HelloWorld3.vue

+21-21
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
<script>
1+
<script setup lang="ts">
22
import { useErrors } from "@/plugins/store";
3-
import { ref } from 'vue';
3+
import { ref, getCurrentInstance, getCurrentScope } from 'vue';
44
5-
export default {
6-
name: 'HelloWorld',
7-
props: {
8-
msg: {
9-
type: String,
10-
required: true
11-
}
12-
},
13-
setup() {
14-
const { errors } = useErrors()
15-
const userEmail = ref("")
5+
const props = defineProps({
6+
msg: {
7+
type: String,
8+
required: true
9+
}
10+
})
1611
17-
const checkError = () => {
18-
console.log(errors.value)
19-
}
12+
const self = getCurrentInstance()?.appContext.config.globalProperties
13+
const { errors } = useErrors()
14+
const userEmail = ref("")
2015
21-
return {
22-
userEmail,
23-
errors,
24-
checkError
16+
const checkError = () => {
17+
self?.$validator.validateAll().then(({isValid}: any) => {
18+
if (isValid) {
19+
console.log('submitted')
20+
return;
2521
}
26-
}
22+
23+
console.log('error - cannot submit')
24+
}).catch((err: any) => {
25+
console.log(err)
26+
})
2727
}
2828
</script>
2929

src/plugins/store.ts

+27-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
1-
import { ref } from 'vue'
1+
import { ref, type VNode } from 'vue'
2+
3+
type ErrorObject = {
4+
scopeID: string
5+
fields: string[]
6+
}
27

38
const errors = ref<Set<string>>(new Set())
9+
const currentErrorsScope = ref<ErrorObject | null>(null)
410
const ConditionSplitChar = ':'
511

612
export const useErrors = () => {
7-
const addError = (el: HTMLInputElement, rule: string = '') => {
8-
const errorValue = rule ? `${el.name}${ConditionSplitChar}${rule}` : `${el.name}`
13+
const addError = (vnode: VNode, rule: string = '') => {
14+
const errorValue = rule ? `${vnode.props?.name}${ConditionSplitChar}${rule}` : `${vnode.props?.name}`
915
errors.value.add(errorValue)
16+
currentErrorsScope.value = {
17+
scopeID: vnode.scopeId || '',
18+
fields: [...currentErrorsScope.value?.fields || [], vnode.props?.name || '']
19+
}
1020
}
1121

12-
const delError = (el: HTMLInputElement, rule: string = '') => {
13-
const errorValue = rule ? `${el.name}${ConditionSplitChar}${rule}` : `${el.name}`
22+
const delError = (vnode: VNode, rule: string = '') => {
23+
const errorValue = rule ? `${vnode.props?.name}${ConditionSplitChar}${rule}` : `${vnode.props?.name}`
1424
errors.value.delete(errorValue)
25+
if (!currentErrorsScope.value?.fields.length) {
26+
currentErrorsScope.value = null
27+
return;
28+
}
29+
30+
currentErrorsScope.value?.fields.splice(currentErrorsScope.value.fields.indexOf(vnode.props?.name || ''), 1)
1531
}
1632

17-
const cleanupErrors = () => {
18-
errors.value.clear()
33+
const cleanupErrors = (vnode: VNode) => {
34+
currentErrorsScope.value = null
35+
errors.value.forEach(() => {
36+
delError(vnode)
37+
})
1938
}
2039

21-
return { errors, addError, delError, cleanupErrors }
40+
return { errors, currentErrorsScope, addError, delError, cleanupErrors }
2241
}

src/plugins/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const validRegex = (rules: string[], el: HTMLInputElement, regex: RegExp)
2222
export const validLength = (rules: string[], el: HTMLInputElement, value: unknown) => {
2323
if (!rules.includes(ValidateTypes.length)) return true
2424
if (rules.includes(ValidateTypes.length) && !value) return true
25-
return rules.includes(ValidateTypes.length) && el.value.length === value
25+
return rules.includes(ValidateTypes.length) && el.value.length === parseInt(value as string)
2626
}
2727

2828
export const validMinLength = (rules: string[], el: HTMLInputElement, value: unknown) => {

src/plugins/validate-type.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const ValidateTypes = {
2-
firstMountedRequired: 'firstMountedRequired',
2+
onMountedRequired: 'onMountedRequired',
33
required: 'required',
44
email: 'email',
55
regex: 'regex',

src/plugins/validate.ts

+38-39
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type App } from 'vue'
1+
import { getCurrentInstance, type App, type VNode } from 'vue'
22
import { useErrors } from './store'
33
import { validEmail, validLength, validRegex, validRequired } from './utils'
44
import ValidateTypes from './validate-type'
@@ -19,7 +19,7 @@ declare global {
1919

2020
export default {
2121
install: (app: App) => {
22-
const { addError, delError, cleanupErrors } = useErrors()
22+
const { errors, currentErrorsScope, addError, delError, cleanupErrors } = useErrors()
2323

2424
app.config.globalProperties.$formMaxLength = {
2525
email: 64,
@@ -35,87 +35,86 @@ export default {
3535
password: 128
3636
}
3737

38+
app.config.globalProperties.$validator = {
39+
validateAll: () => {
40+
return new Promise((resolve, reject) => {
41+
const result = !!currentErrorsScope.value
42+
resolve({
43+
isValid: !result
44+
})
45+
})
46+
},
47+
}
48+
3849
app.directive('validate', {
3950
deep: true,
40-
mounted: (el: HTMLInputElement, binding) => {
41-
const isStringSchema = binding.value instanceof String
51+
mounted: (el: HTMLInputElement, binding, vnode: VNode) => {
52+
const isStringSchema = typeof binding.value === 'string'
53+
const stringRules = isStringSchema ? binding.value.split(SplitChar) : []
4254
const schema = isStringSchema
43-
? binding.value.split(SplitChar).map((rule: string) => {
55+
? stringRules.map((rule: string) =>
4456
rule.includes(':') ? rule.split(':') : [rule, true]
45-
})
57+
)
4658
: Object.entries(binding.value)
4759

48-
binding.value.split(SplitChar).map((rule: string) => {
49-
rule.includes(':') ? console.log(rule.split(':')) : console.log([rule, true])
50-
})
51-
52-
console.log(schema)
53-
54-
const actions = el.dataset.vvValidateOn?.split(SplitChar)
55-
const bubble = binding.modifiers?.bubble
56-
5760
// prevent immediate submission
5861
schema.map(([rule, value]: [string, unknown]) => {
5962
if (rule !== ValidateTypes.required) return
60-
value && addError(el, ValidateTypes.firstMountedRequired)
63+
value && addError(vnode, ValidateTypes.onMountedRequired)
6164
})
6265

63-
const handler = (el: HTMLInputElement) => {
64-
delError(el, ValidateTypes.firstMountedRequired)
66+
const handler = () => {
67+
delError(vnode, ValidateTypes.onMountedRequired)
6568
const rules = schema.map((rule: [string, unknown]) => rule[0])
6669

67-
bubble && binding.value(el)
68-
6970
schema.map(([, value]: [string, unknown]) => {
7071
const satisfyRequired = validRequired(rules, el, !!value)
71-
satisfyRequired && delError(el, ValidateTypes.required)
72-
!satisfyRequired && addError(el, ValidateTypes.required)
72+
satisfyRequired && delError(vnode, ValidateTypes.required)
73+
!satisfyRequired && addError(vnode, ValidateTypes.required)
7374

7475
const satisfyEmail = validEmail(rules, el, value as boolean)
75-
satisfyEmail && delError(el, ValidateTypes.email)
76-
!satisfyEmail && addError(el, ValidateTypes.email)
76+
satisfyEmail && delError(vnode, ValidateTypes.email)
77+
!satisfyEmail && addError(vnode, ValidateTypes.email)
7778

7879
const satisfyRegex = validRegex(rules, el, value as RegExp)
79-
satisfyRegex && delError(el, ValidateTypes.regex)
80-
!satisfyRegex && addError(el, ValidateTypes.regex)
80+
satisfyRegex && delError(vnode, ValidateTypes.regex)
81+
!satisfyRegex && addError(vnode, ValidateTypes.regex)
8182

8283
const satisfyLength = validLength(rules, el, value)
83-
console.log(value)
84-
satisfyLength && delError(el, ValidateTypes.length)
85-
!satisfyLength && addError(el, ValidateTypes.length)
84+
satisfyLength && delError(vnode, ValidateTypes.length)
85+
!satisfyLength && addError(vnode, ValidateTypes.length)
8686

8787
if (!satisfyRequired || !satisfyEmail || !satisfyRegex || !satisfyLength) {
88-
addError(el)
88+
addError(vnode)
8989
} else {
90-
delError(el)
90+
delError(vnode)
9191
}
9292
})
9393
}
9494

9595
el[UniquePropertyID] = handler
9696

97+
const actions = el.dataset.vvValidateOn?.split(SplitChar)
9798
actions?.forEach((action) => {
98-
el.addEventListener(action, () => handler(el))
99+
el.addEventListener(action, () => handler())
99100
})
100101

101-
const form = el.closest('form') as HTMLFormElement
102-
if (![...form.elements].includes(el)) return
103-
102+
const form = el.closest(`form[${vnode.scopeId}]`) as HTMLFormElement
104103
form?.addEventListener('submit', (e) => {
105104
e.preventDefault()
106-
handler(el)
105+
handler()
107106
})
108107
},
109-
unmounted: (el: HTMLInputElement) => {
108+
unmounted: (el: HTMLInputElement, _binding, vnode: VNode) => {
110109
const actions = el.dataset.vvValidateOn?.split(SplitChar)
111110
actions?.forEach((action) => {
112111
el.removeEventListener(action, () => el[UniquePropertyID])
113112
})
114113

115114
// clear cache handler
116115
el[UniquePropertyID] = () => {}
117-
cleanupErrors()
118-
}
116+
cleanupErrors(vnode)
117+
},
119118
})
120119
}
121120
}

0 commit comments

Comments
 (0)