Валидация форм является важной частью frontend-разработки, которая помогает улучшить пользовательский опыт и предотвратить ошибки при отправке данных на сервер. В этой статье мы рассмотрим, как использовать библиотеку AJV совместно с Vue.js и TypeScript для создания мощной системы валидации формы.

Что такое AJV?

AJV (Another JSON Schema Validator) - это быстрая библиотека валидации данных в формате JSON с поддержкой JSON Schema. JSON Schema - это язык описания структуры и валидации данных в формате JSON. AJV позволяет проверять данные по подготовленным схемам валидации.

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

Прежде чем начать, убедитесь, что у вас уже есть:

  • Node.js v18.16.1.

  • @vue/cli 5.0.8

Давайте создадим проект новый проект с помощью Vue CLI с такими параметрами:

vue create ajv-validation
Рис.1 Настройка проекта
Рис.1 Настройка проекта

Установим необходимые зависимости в нашем проекте:

npm install ajv ajv-formats ajv-errors

Создание файлы схемы валидации login.json

{
  "$id": "/login.json",
  "type": "object",
  "additionalProperties": false,
  "required": ["login", "password"],
  "properties": {
    "login": {
      "type": "string",
      "format": "email",
      "errorMessage": "enter a valid email address"
    },
    "password": {
      "type": "string",
      "minLength": 6,
      "maxLength": 1024
    }
  }
}

В этой схеме мы определяем тип каждого поля (строка), а также устанавливаем некоторые правила валидации, такие как формат email и минимальная длина пароля. Поля "login" и "password" обязательны для заполнения.

Давайте кратко пройдёмся по 2 важным методам:

onLogin — это метод, который вызывается при попытке входа пользователя в систему (логине). Он выполняет проверку и валидацию введенных пользователем данных и предпринимает соответствующие действия в зависимости от результата проверки.

  • Проверка введенных данных на корректность с помощью функции валидации validate. Эта функция использует схему валидации данных и проверяет соответствие данных этой схеме. Возвращается флаг isValid, который указывает, прошла ли валидация успешно.

  • Если данные некорректны (isValid равен false), выполняется обработка ошибок.

function onLogin() {
      errors.value.clear();
      if (validator.validate("/login.json", formData.value)) {
        // valid, do nothing
      } else if (validator.errors?.length) {
        for (const [, e] of validator.errors.entries()) {
          if (!e.message) {
            continue;
          }
          const fieldName = e.instancePath.substring(1);
          const fieldErrors: string[] = errors.value.get(fieldName) || [];
          fieldErrors.push(e.message);
          errors.value.set(fieldName, fieldErrors);
        }
      }
    }

onBlur — это метод, который вызывается при событии "blur" (потеря фокуса) на текстовом поле ввода формы. Он используется для валидации данных, введенных пользователем, когда пользователь переходит с поля на другой элемент формы или щелкает вне текстового поля.

  • Получить имя (идентификатор) и значение поля ввода, на котором произошло событие "blur".

  • Получить схему валидации для данного поля.

  • Проверка значения поля на корректность с помощью функции валидации validate.

  • Если значение поля некорректно (isValid равен false), выполняется обработка ошибки.

function onBlur(e: any) {
      const fieldName = e.target.id;
      const fieldValue = e.target.value;
      if (
        validator.validate(`/login.json#/properties/${fieldName}`, fieldValue)
      ) {
        errors.value.delete(fieldName);
      } else if (validator.errors?.length) {
        errors.value.set(
          fieldName,
          validator.errors.map((e) => e.message) as string[]
        );
      }
    }

Заключение

Теперь у вас есть пример, как использовать AJV с Vue.js и TypeScript для валидации формы. Это позволяет создавать мощные и гибкие системы валидации, которые помогут улучшить пользовательский опыт и обеспечить корректную обработку данных на сервере.

Исходный код
<template>
  <div class="login-form">
    <h2>Login</h2>
    <form @submit.prevent="onLogin">
      <div class="form-group">
        <label for="login">Email</label>
        <input
          v-model="formData.login"
          :class="{ 'is-invalid': isInvalid(errors.has('login')) }"
          @blur="onBlur"
          type="text"
          id="login"
          placeholder="Enter your email"
        />
        <ul class="error-wrapper">
          <li v-for="errorMsg of errors.get('login')" :key="errorMsg">
            {{ errorMsg }}
          </li>
        </ul>
      </div>

      <div class="form-group">
        <label for="password">Password</label>
        <input
          v-model="formData.password"
          :class="{ 'is-invalid': isInvalid(errors.has('password')) }"
          @blur="onBlur"
          type="password"
          id="password"
          placeholder="Enter your password"
        />

        <ul class="error-wrapper">
          <li v-for="errorMsg of errors.get('password')" :key="errorMsg">
            {{ errorMsg }}
          </li>
        </ul>
      </div>

      <button type="submit" :disabled="errors.size > 0" @click="submit('ok')">
        Login
      </button>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, watch } from "vue";
import login from "@/schemas/login.json";
import Ajv from "ajv";
import ajvFormats from "ajv-formats";
import ajvErrors from "ajv-errors";

export default defineComponent({
  setup() {
    const formData = ref({
      login: "",
      password: "",
    });

    const opts = { allErrors: true };
    const validator = ajvErrors(ajvFormats(new Ajv(opts)));
    validator.addSchema(login);

    let errors = ref<Map<string, string[]>>(new Map());
    watch(
      () => errors.value,
      () => void 0
    );

    function onBlur(e: any) {
      const fieldName = e.target.id;
      const fieldValue = e.target.value;
      if (
        validator.validate(`/login.json#/properties/${fieldName}`, fieldValue)
      ) {
        errors.value.delete(fieldName);
      } else if (validator.errors?.length) {
        errors.value.set(
          fieldName,
          validator.errors.map((e) => e.message) as string[]
        );
      }
    }

    function onLogin() {
      errors.value.clear();
      if (validator.validate("/login.json", formData.value)) {
        // valid, do nothing
      } else if (validator.errors?.length) {
        for (const [, e] of validator.errors.entries()) {
          if (!e.message) {
            continue;
          }
          const fieldName = e.instancePath.substring(1);
          const fieldErrors: string[] = errors.value.get(fieldName) || [];
          fieldErrors.push(e.message);
          errors.value.set(fieldName, fieldErrors);
        }
      }
    }

    function formColor(error: any) {
      return error?.length;
    }

    function submit(text: string) {
      alert(text);
    }

    return {
      formData,
      errors,
      isInvalid: formColor,
      onBlur,
      onLogin,
      submit,
    };
  },
});
</script>

<style>
body {
  font-family: sans-serif;
}

.login-form {
  max-width: 300px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
  background-color: #f9f9f9;
}

.login-form h2 {
  text-align: center;
  margin-bottom: 20px;
}

.form-group {
  margin-bottom: 20px;
}

label {
  display: block;
  font-weight: bold;
  margin-bottom: 5px;
}

input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 5px;
}

button {
  width: 100%;
  padding: 10px;
  background-color: #007bff;
  color: #fff;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

button:hover {
  background-color: #0056b3;
}

button:disabled {
  background-color: #ccc;
}

.is-invalid {
  border: 1px solid red;
}

.error-wrapper {
  color: red;
  font-size: 12px;
  margin: 0;
  padding: 0 20px;
}
</style>

Комментарии (0)