Към съдържанието
wannacode

Формуляри в компонент

Накратко: v-model във Vue свързва полето на формуляра със състоянието в двете посоки, така че състоянието е единственият източник за проверката и изпращането.

Проблем

Написано, но празно

Купувачът попълва поръчката и натиска „Поръчай“. Сървърът отговаря, че името е задължително. А полето „Име“ показва името.

Vue

Напиши име, имейл и адрес, отметни условията и натисни „Поръчай“. Полето пази „Мария Иванова“, но в заявката заминава name: ''. Имейлът и адресът имат v-model, а името няма. Кодът изпраща състоянието, а то не знае какво е написано в полето.

Идеята

Една истина: състоянието

В урока за DOM формулярът пазеше текста сам и кодът го четеше при изпращане. В компонент има и второ място: състоянието. Ако двете не са свързани, всяко казва своето, както в примера.

Решението е полето да няма своя истина. То показва стойността от състоянието и при всеки клавиш казва на състоянието новия текст. Такова поле се казва контролирано (controlled input).

<script setup>
const name = ref('');
</script>
<template>
<input id="name" v-model="name" />
</template>

v-model е съкратен запис на двете посоки: :value="name" (от състоянието към полето) и @input="name = $event.target.value" (обратно). Събитието input идва при всеки клавиш.

v-model знае вида на полето. Затова на падащ списък и на отметка се пише същото:

<select v-model="payment">…</select>
<input type="checkbox" v-model="agree" />

На отметка agree е true или false, а на списък е value на избраната опция.

Сега състоянието е единственият източник: каквото е в него, това е в полето, и обратното. Бутонът може да се изключи по състоянието (!agree), а заявката изпраща точно каквото вижда купувачът.

Виж го

Виж кръга

Една буква минава целия път: поле, събитие, състояние, ново рисуване и пак поле. После същото поле без връзка.

<script setup>
const name = ref('');
</script>
<template>
<form @submit.prevent="submit">
<label for="name">Име</label>
<input id="name" v-model="name" />
</form>
</template>
Стъпка по стъпка, с думи
  1. Формулярът пази името в състоянието: name = ''. Полето на страницата показва каквото казва състоянието, а то казва: празно.
  2. Купувачът натиска М. Браузърът слага буквата в полето и пуска събитието input.
  3. v-model слуша input. Текстът на полето сега е „М“.
  4. v-model го записва в ref-а: name.value = "М". Това е единственото място, където живее името.
  5. Ref-ът се смени и Vue пуска шаблона наново. v-model дава на полето стойността на name: „М“.
  6. Полето получава стойността си от състоянието. Буквата е същата, но сега състоянието решава какво показва полето.
  7. Всеки клавиш минава целия кръг: поле, събитие, състояние, ново рисуване, поле. Един източник на истината: състоянието.
  8. Сега същото поле без v-model. Полето работи, но никой не го слуша и никой не му дава стойност.
  9. Купувачът пише „Мария“. Буквите остават в DOM-а. Състоянието още казва: name = ''.
  10. При изпращане кодът праща състоянието: { name: '' }. Сървърът отговаря „Името е задължително“, а полето показва „Мария“.
  11. Правилото: всяко поле има v-model, стойност от състоянието и събитие обратно към него. Тогава полето и състоянието не могат да се разминат.

Кръгът изглежда като излишна обиколка: буквата и без него стои в полето. Но заради него състоянието и полето не могат да се разминат.

Пробвай

Вържи полетата

Полето „Име“, падащият списък „Плащане“ и отметката „Приемам условията“ не са вързани за състоянието. Вържи и трите. Бутонът „Поръчай“ е изключен, докато условията не са приети. Проверката пише име, имейл и адрес, избира „С карта при доставка“, отметва и маха отметката и после поръчва. Очаква „Поръчка № 61 е приета.“ и заявка с написаното име и payment: 'card'.

Vue

Пробвай

Попълни

Какво стои на празното място, за да е вързана отметката?

<script setup>
const agree = ref(false);
</script>

<template>
  <input type="checkbox" ="agree" />
</template>

Пробвай

Намери грешката

Купувачът пише името си и то се вижда, но формулярът казва, че е празно. Имейлът работи. Кой ред е виновен?

Избери реда с грешката.

Идеята

Проверка до полето

Сървърът проверява всяка поръчка. Купувачът обаче не трябва да чака отговор, за да разбере, че е забравил адреса. Затова формулярът проверява и сам, преди да изпрати.

  1. Кога. При натискане на „Поръчай“. Ако има грешки, заявка не тръгва.
  2. Какво. Функция check() връща обект с грешките по поле: { name: 'Напиши името си', email: … }. Празен обект значи „всичко е наред“. Той се пази в състоянието errors.
  3. Къде. До полето, а не в общ списък горе. Купувачът вижда кое поле да поправи.
  4. За всеки. Екранният четец не вижда, че текстът е червен. Затова полето получава aria-invalid="true" (четецът казва „невалидно“), а съобщението получава id, към който полето сочи с aria-describedby. Когато фокусът дойде на полето, четецът прочита и съобщението.
<input
id="email"
v-model="email"
:aria-invalid="!!errors.email"
:aria-describedby="errors.email ? 'email-error' : undefined"
/>
<p v-if="errors.email" id="email-error" class="error">{{ errors.email }}</p>

Формулярът е с novalidate. Без него браузърът спира изпращането със свои балончета, преди да стигне до твоя код, и двете проверки си пречат.

Сървърът проверява пак. Проверката в браузъра е за удобството на купувача. Тя не пази магазина: кодът на страницата е при купувача и заявка може да дойде и без нея. Защо е така има в урока „Недоверен вход“.

Пробвай

Провери преди изпращане

Добави проверка преди изпращане. Празно име дава „Напиши името си“, имейл без @ дава „Имейлът трябва да съдържа @“, празен адрес дава „Напиши адрес за доставка“. Всяко съобщение е до полето си, в елемент с id name-error, email-error и address-error, а полето има aria-invalid=true и aria-describedby към него. С грешки заявка не тръгва. Проверката отметва условията, поръчва празен формуляр, после с имейл без @ и накрая с всичко вярно.

Vue

Идеята

Изпращане и отговорът на сървъра

Заявката отнема време, а купувачът не знае дали кликът е стигнал. Ако кликне пак, заминава втора поръчка. Затова състоянието има и sending:

  1. Преди fetch sending става true. Бутонът е disabled, а до него пише „Изпраща се…“.
  2. След отговора sending става false, каквото и да е дошло.
  3. 201: в тялото е { id }. Формулярът се сменя със „Поръчка № 61 е приета.“
  4. 400: в тялото е { errors: { name, email, address, items } }, по едно съобщение на поле. Те отиват в същото errors, затова се показват на същите места до полетата. items няма поле: показва се до реда с кошницата.
sending.value = true;
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name.value, email: email.value, /* … */ items: ITEMS }),
});
const data = await res.json();
sending.value = false;
if (res.ok) orderId.value = data.id;
else errors.value = data.errors;

Проверката в браузъра е по-проста от тази на сървъра: тя търси само „@“. Имейлът maria@shop я минава, а сървърът го връща с „Имейлът не е валиден“. Затова формулярът трябва да може да покаже и грешка, за която сам не е знаел.

По-точно, за любопитните

Модификатори на v-model. v-model.trim маха интервалите в началото и края, v-model.number превръща текста в число (за type="number"), а v-model.lazy обновява при change (когато полето загуби фокус), не при всеки клавиш.

:value без събитие. Полето показва name, буквите влизат в DOM-а, но name не се сменя. Когато компонентът се рисува наново по друга причина (например се появи съобщение за грешка), Vue връща полето към стойността на name и написаното изчезва.

Обект вместо пет ref-а. Може и const form = reactive({ name: '', email: '', … }) с v-model="form.name". Vue следи свойствата на обекта, затова v-model работи на всяко от тях.

v-model на компонент е :modelValue надолу и @update:modelValue нагоре, както в урока за общото състояние нагоре. Така се правят полета-компоненти (<MoneyInput v-model="price" />).

Кога да проверяваш. При изпращане е най-простото. Проверка при всеки клавиш показва грешки, преди купувачът да е довършил („имейлът е невалиден“ след първата буква). Честото решение е: първа проверка при изпращане или при напускане на полето (blur), а после при всеки клавиш, за да изчезне грешката, щом е поправена.

Фокус при грешка. След неуспешно изпращане е удобно фокусът да отиде на първото поле с грешка. Така клавиатурата и екранният четец стигат направо до проблема.

Проверката не може да види кода. Задачите тук проверяват поведението: какво се вижда, какво е изключено, какво заминава. Те не виждат дали проверката е в отделна функция или как са кръстени променливите.

Упражнение

Поръчката докрай

Сега формулярът проверява в браузъра, но не показва, че изпраща, а грешките на сървъра събира в един абзац. Направи го като истинска поръчка: докато заявката върви, бутонът „Поръчай“ е изключен и има текст „Изпраща се…“ (с role=status). При отговор 400 грешките от data.errors отиват до полетата, на същите места като твоите (email-error и aria-invalid на имейла), а грешката за items до реда с кошницата. Проверката пише имейл maria@shop: той минава твоята проверка, но сървърът го връща с „Имейлът не е валиден“. После добавя .bg и поръчката минава: „Поръчка № 61 е приета.“

Vue

Тест

Провери се

1. Полето „Име“ показва „Мария“, а състоянието name е празно. Кое заминава към сървъра?
2. Полето е <input :value="name" />, без v-model. Какво става, когато купувачът пише?
3. Какво дава v-model на отметка (type="checkbox")?
4. Проверката в браузъра казва, че имейлът е наред. Трябва ли сървърът да го проверява пак?
5. Защо бутонът „Поръчай“ се изключва, докато заявката върви?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·