У меня есть проверка поля, и во время отправки мне нужно сосредоточиться на первом поле ввода, в котором есть ошибки.
из моего родительского компонента CustomForm я не могу получить доступ к вводу дочернего компонента в CustomInput
<script setup lang = "ts">
import CustomForm from '@/components/CustomForm.vue'
</script>
<template>
<CustomForm />
</template>
<style scoped lang = "scss">
</style>
<script setup lang = "ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'
const formData = [
{
name: 'title',
label: 'title',
required: true,
value: '',
isValid: false,
errorMessages: []
},
{
name: 'name',
label: 'name',
required: true,
type: 'textarea',
value: '',
isValid: false,
errorMessages: []
}
]
const { isFormValid, fieldsForm, submit } = useForm(formData)
const submitForm = () => {
submit()
if (isFormValid.value) {
console.info('submit')
console.info(fieldsForm)
}
}
</script>
<template>
<form @submit.prevent = "submitForm">
<CustomInput v-for = "data in fieldsForm"
:key = "data.name"
ref = "customInputRef"
:field-data = "data"
v-model = "data.value"
v-model:error = "data.errorMessages"
v-model:isValid = "data.isValid"
/>
<button type = "submit">Отправить</button>
</form>
</template>
<style scoped lang = "scss">
</style>
<script setup lang = "ts">
import { computed, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'
const props = withDefaults(defineProps<IFieldProps>(), {
placeholder: (props: IFieldProps) => `Input ${props.fieldData.name.toUpperCase()} please`
})
const emit = defineEmits([
'update:modelValue',
'update:error',
'update:isValid',
])
const r= ref(null)
const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)
const { isBlurred,field, blurAction,inputAction, errors, isValid } = useInputValidator(props.fieldData)
const inputHandler = (e: Event) => {
const v = (e.target as HTMLInputElement).value
emit('update:modelValue', v)
inputAction()
emit('update:error', errors)
}
const blurHandler = (e: Event) => {
blurAction()
emit('update:error', errors)
emit('update:isValid', isValid)
}
</script>
<template>
<div class = "field">
<label class = "field__label"
:for = "inputId">
{{ field.label }}
</label>
<div class = "field__inner">
<div class = "field-icon" v-if = "$slots.icon">
<slot name = "icon"></slot>
</div>
<component :is = "inputComponent"
:name = "field.name"
ref = "r"
class = "field__input"
:class = "{
valid:field.isValid,
error:field.errorMessages.length,
}"
:id = "inputId"
:value = "field.value"
@input = "inputHandler"
@blur = "blurHandler"
:placeholder = "props.placeholder" />
<template v-if = "field.errorMessages">
<p class = "field__error" v-for = "(error,index) in field.errorMessages" :key = "index">
{{ error }}
</p>
</template>
</div>
</div>
</template>
import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import { validateField } from '@/helpers/validateField'
export const useForm = (formFields: Field[]) => {
const fieldsForm = ref(formFields)
const isFormValid = computed(() =>
fieldsForm.value.every(field => field.isValid)
)
const updateValidity = (fieldName: string, errors: string[]) => {
const field = fieldsForm.value.find(data => data.name === fieldName)
if (field) {
field.errorMessages = errors
}
}
const checkFields = () => {
fieldsForm.value.forEach(field => {
let err: string[] = []
if (field.required) {
if (!isFormValid.value && !field.errorMessages.length) {
err = validateField(field.name, field.value)
updateValidity(field.name, err)
}
}
})
}
const submit = () => {
checkFields()
}
return {
submit,
isFormValid,
fieldsForm
}
}
import { computed, ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'
import { validateField } from '@/helpers/validateField'
export const useInputValidator = (fieldForm: Field) => {
const field = ref<Field>(fieldForm)
const errors = ref(field.value.errorMessages)
const isBlurred = ref(false)
const isValid = computed(() => {
return !errors.value.length
})
watchEffect(()=>{
if (field.value.errorMessages.length){
isBlurred.value= true
errors.value= field.value.errorMessages
}
})
const inputAction = () => {
if (isBlurred.value) {
errors.value = validateField(field.value.name, field.value.value)
}
}
const blurAction = () => {
if (isBlurred.value) return
errors.value = validateField(field.value.name, field.value.value)
isBlurred.value =true
}
return {
field,
isValid,
errors,
blurAction,
inputAction,
isBlurred
}
}
Я проверил поля. Но функционал с фокусом остался. Я хотел бы иметь доступ к полям формы из хуков как изменить данные основного массива например isValid? .................................................. .................................................. ..
Во-первых, чтобы применить Refs внутри v-for, используйте массив или функцию согласно документации.
Во-вторых, в дочернем компоненте используйте defineExpose()
для привязки к требуемому элементу Ссылка на компонент.
Я создал простой пример:
/* App.vue */
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const formData = ref({
firstName: '',
lastName: '',
})
const inputs = ref({});
const focus = (key) => {
inputs.value[key].input.focus();
}
</script>
<template>
<div class = "inputs">
<CustomInput
v-for = "(value, key) in formData"
v-model = "formData[key]"
:ref = "el => inputs[key] = el"
:label = "key"
></CustomInput>
</div>
<br>
<div class = "btns">
<button
v-for = "(value, key) in inputs"
@click = "focus(key)"
>
Focus to {{ key }}
</button>
</div>
<br>
<pre>{{ formData }}</pre>
</template>
<style>
.inputs {
display: grid;
gap: 4px;
}
.btns {
display: flex;
gap: 8px;
}
</style>
/* CustomInput.vue */
<script setup>
import { ref } from 'vue';
defineProps({
label: String,
})
const model = defineModel();
const input = ref();
defineExpose({ input });
</script>
<template>
<label>
{{ label }}:
<br>
<input
ref = "input"
v-model = "model"
>
</label>
</template>
Привет! Если вы опубликуете пример рабочего кода, воспроизводящий проблему, я постараюсь помочь. В моем примере inputs.value
— это объект типа {firstName: { input: HTMLInputElement }, lastName: { input: HTMLInputElement }}
. То есть, если вы хотите сосредоточиться на первом поле: inputs.value.firstName.input.focus();
Я реализовал код, который в целом работает так, как мне нужно. но мне кажется, что есть лучший вариант. Скажи мне как?
import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import type { Ref, UnwrapRef } from 'vue'
import { validateField } from '@/helpers/validateField'
import CustomInput from '@/components/CustomInput.vue'
export const useForm = (formFields: Field[],
inputs: Ref<UnwrapRef<InstanceType<typeof CustomInput>>>) => {
const fieldsForm = ref(formFields)
const isFormValid = computed(() =>
fieldsForm.value.every(field => field.isValid)
)
const updateValidity = (fieldName: string, errors: string[]) => {
const field = fieldsForm.value.find(data => data.name === fieldName)
if (field) {
field.errorMessages = errors
}
}
const focus = () => {
const arr = fieldsForm.value.findIndex((item) => {
return item.errorMessages.length > 0
})
if (arr !== -1 && inputs.value[arr] && inputs.value[arr].input) {
inputs.value[arr].input.focus()
}
}
const checkFields = () => {
fieldsForm.value.forEach((field) => {
let err: string[] = []
if (field.required) {
if (!isFormValid.value && !field.errorMessages.length) {
err = validateField(field.name, field.value)
updateValidity(field.name, err)
}
}
})
}
const submit = () => {
if (isFormValid.value) return
checkFields()
focus()
}
return {
submit,
isFormValid,
fieldsForm
}
}
<script setup lang = "ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'
import { ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'
const formData = [
{
name: 'title',
label: 'title',
required: true,
value: '',
isValid: false,
errorMessages: []
},
{
name: 'name',
label: 'name',
required: true,
type: 'textarea',
value: '',
isValid: false,
errorMessages: []
}
]
const inputs = ref<InstanceType<typeof CustomInput>>([])
const { isFormValid, fieldsForm, submit } = useForm(formData, inputs)
const submitForm = () => {
submit()
if (isFormValid.value) {
console.info('submit')
}
}
</script>
<template>
<form @submit.prevent = "submitForm">
<CustomInput v-for = "(data,key) in fieldsForm"
:key = "data.name"
:ref = "(el:HTMLInputElement|null) => inputs[key] = el"
:field-data = "data"
v-model = "data.value"
v-model:error = "data.errorMessages"
v-model:isValid = "data.isValid"
/>
<button type = "submit">Отправить</button>
</form>
</template>
<style scoped lang = "scss">
</style>
<script setup lang = "ts">
import { computed, onUpdated, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'
const props = withDefaults(defineProps<IFieldProps>(), {
placeholder: (props: IFieldProps) => `Input ${props.fieldData.name.toUpperCase()} please`
})
const emit = defineEmits([
'update:modelValue',
'update:error',
'update:isValid'
])
const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)
const { isBlurred, field, blurAction, inputAction, errors, isValid } = useInputValidator(props.fieldData)
const inputHandler = (e: Event) => {
const v = (e.target as HTMLInputElement).value
emit('update:modelValue', v)
inputAction()
emit('update:error', errors)
}
const blurHandler = (e: Event) => {
blurAction()
emit('update:error', errors)
emit('update:isValid', isValid)
}
const input = ref<HTMLInputElement>()
defineExpose({
input,
})
</script>
<template>
<div class = "field">
<label class = "field__label"
:for = "inputId">
{{ field.label }}
</label>
<div class = "field__inner">
<div class = "field-icon" v-if = "$slots.icon">
<slot name = "icon"></slot>
</div>
<component :is = "inputComponent"
ref = "input"
:name = "field.name"
class = "field__input"
:class = "{
valid:field.isValid,
error:field.errorMessages.length,
}"
:id = "inputId"
:value = "field.value"
@input = "inputHandler"
@blur = "blurHandler"
:placeholder = "props.placeholder" />
<template v-if = "field.errorMessages">
<p class = "field__error" v-for = "(error,index) in field.errorMessages" :key = "index">
{{ error }}
</p>
</template>
</div>
</div>
</template>
Добро пожаловать в Stack Overflow. Пожалуйста, возьмите экскурсию . Это сайт вопросов и ответов, а не дискуссионный форум. Этот раздел предназначен только для ответов. Если вы хотите обновить свой вопрос, отредактируйте его.
пожалуйста, помогите мне с моим кодом. Я сделал, как вы предложили. и я вхожу в консоль в свой родительский console.info(inputs.value), и он показывает мне, что существует массив {0: Proxy(Object), 1: Proxy(Object)}, но я не могу получить доступ к своему вводу. подскажите, как с учетом моего кода правильно реализовать функционал фокусировки на поле, где при отправке возникают ошибки? Я пытаюсь инкапсулировать логику в хуке useForm. Есть ли у вас предложения по улучшению моего кода?