vue2_rus

Правильность заполнения форм

Простой пример

Проверка форм встроенно поддерживается обозревателями, но иногда разные обозреватели могут обрабатывать вещи таким образом, что полагаться на них будет сложнее. Даже когда поддержка правильности реализована на отлично — могут потребоваться специальные проверки, и более подходящие решения основанные на Vue. Начнём с простого примера.

Есть форма с тремя полями, сделаем два из них обязательными. Давайте посмотрим на HTML для решения этой задачи:

<form
  id="app"
  @submit="checkForm"
  action="https://vuejs.org/"
  method="post"
>

  <p v-if="errors.length">
    <b>Пожалуйста исправьте указанные ошибки:</b>
    <ul>
      <li v-for="error in errors">{{ error }}</li>
    </ul>
  </p>

  <p>
    <label for="name">Имя</label>
    <input
      id="name"
      v-model="name"
      type="text"
      name="name"
    >
  </p>

  <p>
    <label for="age">Возраст</label>
    <input
      id="age"
      v-model="age"
      type="number"
      name="age"
      min="0"
    >
  </p>

  <p>
    <label for="movie">Любимый фильм</label>
    <select
      id="movie"
      v-model="movie"
      name="movie"
    >
      <option>Star Wars</option>
      <option>Vanilla Sky</option>
      <option>Atomic Blonde</option>
    </select>
  </p>

  <p>
    <input
      type="submit"
      value="Отправить"
    >
  </p>

</form>

Начнём с начала. Тег <form> имеет ID, который мы будем использовать для компонента Vue. Есть обработчик события отправки формы (submit), который мы увидим далее, и также атрибут action с временным URL-адресом, который будет указывать на что-то реальное где-нибудь на сервере (где конечно же будет реализована и проверка на стороне сервера).

После этого расположен абзац, который показывается или скрывается в зависимости от наличия ошибок. Это позволит отобразить простой список ошибок над формой. Также обратите внимание, что мы запускаем проверку на submit формы, а не при изменении каждого поля.

Последнее, что нужно отметить — каждое из трёх полей имеет соответствующую v-model для связи с значениями, с которыми мы будем работать в JavaScript. Теперь давайте посмотрим на код.

const app = new Vue({
  el: '#app',
  data: {
    errors: [],
    name: null,
    age: null,
    movie: null
  },
  methods: {
    checkForm: function (e) {
      if (this.name && this.age) {
        return true;
      }

      this.errors = [];

      if (!this.name) {
        this.errors.push('Требуется указать имя.');
      }
      if (!this.age) {
        this.errors.push('Требуется указать возраст.');
      }

      e.preventDefault();
    }
  }
})

Довольно коротко и просто. Мы определяем массив для хранения ошибок и задаём null в качестве значения для всех трёх полей формы. Логика checkForm (которая запускается при отправке формы) проверяет только имя и возраст, оставляя поле выбора фильма вариативным. Если они пусты, то мы проверяем по очереди и устанавливаем конкретную ошибку для каждого поля. И это в принципе всё. Вы можете запустить демо по ссылке ниже. Не забывайте, что при успешной отправке формы будет отправляться POST-запрос на временный URL.

Посмотрите Pen валидация форм 1 от Raymond Camden (@cfjedimaster) на CodePen.

Использование пользовательских проверок

Во втором примере, второе текстовое поле (возраст) было заменено на email, который будет проверяться с помощью специальной логики. Код взят из вопроса на StackOverflow, Как проверить электронный адрес в JavaScript?. Классный вопрос — на его фоне любые политические/религиозные святовойны выглядят как незначительные разночтения насчёт сортов пива. Серьёзно — это безумие. Вот HTML, хотя он действительно близок к первому примеру.

<form
  id="app"
  @submit="checkForm"
  action="https://vuejs.org/"
  method="post"
  novalidate="true"
>

  <p v-if="errors.length">
    <b>Пожалуйста исправьте указанные ошибки:</b>
    <ul>
      <li v-for="error in errors">{{ error }}</li>
    </ul>
  </p>

  <p>
    <label for="name">Имя</label>
    <input
      id="name"
      v-model="name"
      type="text"
      name="name"
    >
  </p>

  <p>
    <label for="email">Email</label>
    <input
      id="email"
      v-model="email"
      type="email"
      name="email"
    >
  </p>

  <p>
    <label for="movie">Любимый фильм</label>
    <select
      id="movie"
      v-model="movie"
      name="movie"
    >
      <option>Звёздные войны</option>
      <option>Ванильное небо</option>
      <option>Взрывная блондинка</option>
    </select>
  </p>

  <p>
    <input
      type="submit"
      value="Отправить"
    >
  </p>

</form>

Хотя изменения достаточно малы, обратите внимание на novalidate="true" наверху. Это важно, потому что обозреватель будет пытаться проверить email адрес в поле с типом type="email". Честно говоря, в этом случае больше смысла доверять обозревателю, но, поскольку нам нужен пример с пользовательской проверкой, мы отключим его. Вот обновлённый JavaScript.

const app = new Vue({
  el: '#app',
  data: {
    errors: [],
    name: null,
    email: null,
    movie: null
  },
  methods: {
    checkForm: function (e) {
      this.errors = [];

      if (!this.name) {
        this.errors.push('Укажите имя.');
      }
      if (!this.email) {
        this.errors.push('Укажите электронную почту.');
      } else if (!this.validEmail(this.email)) {
        this.errors.push('Укажите корректный адрес электронной почты.');
      }

      if (!this.errors.length) {
        return true;
      }

      e.preventDefault();
    },
    validEmail: function (email) {
      var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
      return re.test(email);
    }
  }
})

Как вы можете видеть, мы добавили validEmail в качестве нового метода и просто вызываем его из checkForm. Вы можете поиграть с этим примером здесь:

Посмотреть Pen валидации форм 2 от Raymond Camden (@cfjedimaster) на CodePen.

Другой пример пользовательской проверки

Для третьего примера, мы создадим то, что вы вероятно видели в приложениях для опросов. Пользователю предлагается потратить «бюджет» на набор возможностей новой модели Звездобоя. Сумма должна быть равна 100. Во-первых, HTML.

<form
  id="app"
  @submit="checkForm"
  action="https://vuejs.org/"
  method="post"
  novalidate="true"
>

  <p v-if="errors.length">
    <b>Пожалуйста исправьте указанные ошибки:</b>
    <ul>
      <li v-for="error in errors">{{ error }}</li>
    </ul>
  </p>

  <p>
    Учитывая бюджет в 100 рублей, укажите сколько
    вы хотите потратить на следующие возможности для
    Звездобой следующего поколения. Ваша сумма не должна превысить 100.
  </p>

  <p>
    <input
      type="number"
      name="weapons"
      v-model.number="weapons"
    > Орудия <br/>
    <input
      type="number"
      name="shields"
      v-model.number="shields"
    > Броня <br/>
    <input
      type="number"
      name="coffee"
      v-model.number="coffee"
    > Кофе <br/>
    <input
      type="number"
      name="ac"
      v-model.number="ac"
    > Кондиционирование <br/>
    <input
      type="number"
      name="mousedroids"
      v-model.number="mousedroids"
    > Мыши-дройды <br/>
  </p>

  <p>
    Итого: {{ total }}
  </p>

  <p>
    <input
      type="submit"
      value="Отправить"
    >
  </p>

</form>

Обратите внимание на набор полей охватывающих пять разных функций. Обратите внимание на использование .number в атрибуте v-model. Это говорит Vue преобразовывать значение в число, когда вы его используете. Однако, здесь есть ошибка в это функции — когда значение пустое, то оно будет превращаться в строку. Вы увидите ниже как можно обойти эту проблему. Чтобы сделать это немного проще для пользователя, мы также добавили итоговую сумму внизу, чтобы они могли видеть в реальном времени, на какую сумму выбрано возможностей. Теперь давайте посмотрим на JavaScript.

const app = new Vue({
  el: '#app',
  data: {
    errors: [],
    weapons: 0,
    shields: 0,
    coffee: 0,
    ac: 0,
    mousedroids: 0
  },
  computed: {
     total: function () {
       // необходимо разбирать, потому что Vue
       // преобразует пустое значение в строку
       return Number(this.weapons) +
         Number(this.shields) +
         Number(this.coffee) +
         Number(this.ac + this.mousedroids);
     }
  },
  methods: {
    checkForm: function (e) {
      this.errors = [];

      if (this.total != 100) {
        this.errors.push('Итоговая сумму должна быть 100!');
      }

      if (!this.errors.length) {
        return true;
      }

      e.preventDefault();
    }
  }
})

Мы указали итоговую сумму как вычисляемое свойство, и это было достаточно просто реализовать для обхода этой ошибки. Мой метод checkForm теперь просто должен проверять является ли значение total равным 100 им всё. Вы можете поиграть с этим примером здесь:

Посмотрите Pen валидации форм 3 от Raymond Camden (@cfjedimaster) на CodePen.

Проверка на стороне сервера

В последнем примере мы построили что-то, что использует Ajax для проверки на сервере. Форма предложит назвать новый продукт и затем проверит, чтобы имя было уникальным. Мы быстро накидали Netlify serverless действие для этой проверки. Хотя это не очень важно, вот эта логика:

exports.handler = async (event, context) => {

  const badNames = ['vista', 'empire', 'mbp'];
  const name = event.queryStringParameters.name;

  if (badNames.includes(name)) {
    return {
      statusCode: 400,
      body: JSON.stringify({error: 'Invalid name passed.'})
    }
  }

  return {
    statusCode: 204
  }
}

В принципе, любое имя кроме «vista», «empire», и «mbp» приемлемы. Хорошо, давайте посмотрим на форму.

<form
  id="app"
  @submit="checkForm"
  method="post"
>

  <p v-if="errors.length">
    <b>Пожалуйста исправьте указанные ошибки:</b>
    <ul>
      <li v-for="error in errors">{{ error }}</li>
    </ul>
  </p>

  <p>
    <label for="name">Имя нового продукта: </label>
    <input
      id="name"
      v-model="name"
      type="text"
      name="name"
    >
  </p>

  <p>
    <input
      type="submit"
      value="Отправить"
    >
  </p>

</form>

Здесь нет ничего особенного. Давайте посмотрим на JavaScript.

const apiUrl = 'https://vuecookbook.netlify.com/.netlify/functions/product-name?name=';

const app = new Vue({
  el: '#app',
  data: {
    errors: [],
    name: ''
  },
  methods: {
    checkForm: function (e) {
      e.preventDefault();

      this.errors = [];

      if (this.name === '') {
        this.errors.push('Укажите имя продукта.');
      } else {
        fetch(apiUrl + encodeURIComponent(this.name))
          .then(async res => {
            if (res.status === 204) {
              alert('OK');
            } else if (res.status === 400) {
              let errorResponse = await res.json();
              this.errors.push(errorResponse.error);
            }
          });
      }
    }
  }
})

Мы начали с переменной, определяющей URL-адрес нашего API, который работает на OpenWhisk. Теперь посмотрим на checkForm. В этой версии мы всегда предотвращаем отправку формы (что, кстати, можно было бы сделать и в HTML вместе с Vue). Вы можете увидеть простую проверку на случай если this.name пуста, а затем мы делаем запрос на API. Если всё плохо — мы добавляем ошибку, как и раньше. Если всё хорошо — пока ничего не делаем (просто показываем alert), но вы можете перенаправить пользователя на новую страницу с именем продукта в URL или выполнять другие действия. Вы можете посмотреть демо по ссылке ниже:

Посмотрите Pen валидация форм 4 от Raymond Camden (@cfjedimaster) на CodePen.

Альтернативы

В то время как cookbook сосредотачивается на том, чтобы сделать проверку формы своими руками, существует конечно и много отличных библиотек Vue, которые сделают большую часть работы за вас. Переход к использованию подготовленной библиотеки может повлиять на итоговый размер вашего приложения, но преимущества могут быть огромны. У вас есть код, который (скорее всего) основательно протестирован и регулярно обновляется. Некоторые примеры библиотек для проверки смотрите тут: