mgr-forms-react: Простой компонент для простейших форм

Вы когда нибудь считали, сколько форм вы делаете во время разработки веб-приложения? И я не говорю о сложных формах вроде кастомного date-picker’а или же чего-то сложнее, а простых форм с тремя input, двумя select и одним textarea?
Я не считал. Но когда я начал писать очередное приложение на React и мне за один вечер пришлось создать 5 разных форм — мне поплохело. Ну, а когда разработчику плохеет — разработчик пишет велосипед!


81b5ed70f76f4aa88648de3855b11fe1.png

Из таких вот соображений на свет появилась пока еще сырая, но уже используемая мной в двух разных проектах, библиотека для создания простейших форм на React. И я даже выделю слово простейших, потому как моя поделка даже близко не стоит рядом с такими проектами как React Forms или же Formsy-React.


Вместо картинки для привлечения внимания — количество однотипного кода, который нам всем приходится писать ради создания простейшей формы с одним полем.


Постановка задачи

После некоторого размышления я осознал, что хочу иметь под рукой React-компонет, готовый:


  • Рендерить форму состоящую из простейших элементов (input, select, textarea) каждый из которых легко настраивается (placeholder, default value, type для input, и так далее)…
  • … и имеющую одну лишь кнопку Submit, текст и поведение которой настраивается…
  • … не имеющую готовых стилей, но способную принимать классы к своим элементам.
  • Автоматически собирать данные из самой формы в плоский JSON объект с заданными ключами.
  • Иметь простейший механизм валидации полей в форме.
  • Показывать ошибки, связанные как с каким-то конкретным полем формы, так и общую ошибку для всей формы.

Также через некоторое время добавилось еще одно требование:


  • Каким либо образом описание формы (но не сам рендеринг) должен быть идентичным для React и React Native.

Последние требование появилось после решения пилить проект на React-native. Очень уж не хотелось переписывать все формы по второму разу.


Решение

Вариантом, который в данный момент меня устраивает почти по всем пунктам стал mgr-form-react. Что-то там ещё не доработано, что-то забыто, но всё настолько просто, что может быть добавлено очень быстро и безболезненно. Но давайте пройдемся по главным идеям.


Сразу пример простейшей формы из документации на GitHub:


import Form from "mgr-form-react";
export default TestComponent = () => {
  // Описание полей формы
  const controls = [
    {
      element: 'input', // тип поля. Может быть input, select, textarea
      id: 'Signup.Client.Form.Control.Name', // id DOM-елемента самого control'а
      label: 'Client name', //текст показаный на лэйбле рядом с полем
      type: 'text' // тип input'а
    }, {
      element: 'select',
      id: 'Signup.Client.Form.Control.Language',
      label: 'Client language',
      options: ['en', 'fr', 'it', 'de', 'ru', 'es'] // список возможных значений для select'а
    }
  ];

  // Описание поведения и текста кнопки в форме
  const submit = {
    text: 'Submit button text',
    cb: (data) => {
      console.log(data);
    }
  };

  // Флаг говорящий является ли форма активной
  const editable = true;

  return 
; }

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


Сейчас готовится такой же компонент для React-native. Плюс формат описания формы в виде JSON-документа я вижу в том, что он может быть определен на сервере и считываться клиетном. Таким образом нам не нужно будет ждать 2 недели в среднем пока клиенты обновят свое приложение, чтобы запретить им, допустим, оставлять фидбэк без электронного адреса для связи.


Пока что количество настраиваемых параметров формы не очень велико, но в планах есть добавление различных функций, как например определение поведения onInput, onChange, onFocus, onBlur для полей формы, добавление кнопок, регистрация собственных типов полей (то есть если вам нужно использовать что-либо более сложное чем input, select или textarea, должна быть возможность зарегистрировать ваш компонент и использовать его в описании формы) и так далее. Ну и, конечно же, проект открыт для пул-реквестов.


Полный список параметров компонента

Полная документация находится на GitHub, здесь же — просто список.


  • controls: массив с объектами, описывающими поля формы. Порядок полей будет соблюден.
     — element
     — id
     — label
     — default
     — data
     — class
     — options
     — placeholder
     — type
     — validator
     — formatError
  • submit: объект описывающий текст и поведение кнопки в форме
     — text
     — cb
     — class
  • errors: объект содержащий ошибки которые должны быть показаны в форме. Возможные поля — general для глобальной ошибки всей формы, или же значения полей id объектов описания формы
  • editable: true или false, индикатор активности формы.

Пример использования всех возможных параметров компонента
import Form from 'mgr-form-react';
export default TestComponent = () => {
  const controls = [
    {
      element: 'input',
      id: 'Signup.Client.Form.Control.Name',
      label: 'Client name',
      placeholder: 'Client name',
      default: 'Default name value',
      type: 'text',
      data: 'name', // аргумент фунции cb в параметра submit для компонента формы будет иметь поле с ключом "name" и значением данного поля формы
      validator: /^[A-Za-z0-9\s]{3,30}$/, // регулярное выражения для валидации значения поля
      formatError: 'Wrong name format', // Ошибка, показанная в случае, когда значение поля не проходит вадидацию
      class: 'custom-input-class' // css-класс который будет добавлен к данному полю формы
    }, {
      element: 'select',
      id: 'Signup.Client.Form.Control.Language',
      label: 'Client language',
      options: ['en', 'fr', 'it', 'de', 'ru', 'es'],
      default: 'en',
      data: 'language',
      class: 'custom-select-class'
    }
  ];

  const submit = {
    text: 'Submit button text',
    cb: (data) => {
      console.log(data); // { name: "Default name value", language: "en" } в случае дефолтных значений каждого из полей
    }
  };

  const errors = {
    'Signup.Client.Form.Control.Name': 'Name field error that is generated by someone outside of the form (e.g. server response error)', // будет показано рядом с полем имеющим id  'Signup.Client.Form.Control.Name'.
    general: 'A general error that will be shown under the form itself' // будет показано в отдельном div'е
  };

  const editable = true;

  return ;
}

Стили

Как было сказано выше, мне требовался компонент, генерирующий формы без стилей. Таким образом, стили добавляются уже независимо от того, каким образом была создана форма — используя mgr-form-react или же каким-либо другим способом.
Структура классов в сгенерируемой форме будет следующей:


  • mgrform-form — класс обертка для всей формы
  • mgrform-control — класс — обертка для каждого поля формы.
  • mgrform-has-error — класс, добавляющийся к mgrform-control в случае наличия ошибки для данного поля (либо ошибки валидации, либо ошибки из параметров компонента)
  • mgrform-submit-btn — класс кнопки формы
  • mgrform-error — класс div элемента, показывающего глобальную ошибку формы.
  • к элементам с классами mgrform-control и mgrform-submit-btn добавляются классы, заданные в свойствах class объектов описания поля формы (объекта в массиве controls) и объекта submit.

Заключение

Я вполне понимаю, что это хороший пример велосипедостроения. Однако, мне очень удобно описывать простые формы, не требующие сложной логики внутри себя, в каком-либо виде, возможном для сохранения в отдельный файл с возможностью последующего переиспользования в других проектах.


Само собой я открыт для критики, и разумеется буду рад, если кому нибудь данная поделка сохранит 5–10 минут рабочего времени, которые можно будет потратить на еще одну чашку кофе.


P.S. я не особо гуглил существующие решения похожего типа, буду рад, если кто-нибудь укажет мне на них. Спасибо

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

  • 1 ноября 2016 в 00:03

    +1

    Было бы удобно настраивать шаблон элемента отдельно и в конфиге давать только его имя и интегрировать с бутстрапом. Сейчас то же ищу замену Angular-Formly.JS для переезда на React. Их вариант как-то подздох :(
    • 1 ноября 2016 в 00:11 (комментарий был изменён)

      +1

      Я это и имел ввиду, когда говорил про регистрацию собственных типов полей. Ну то есть что-то вроде
      import Form from "mgr-form-react";
      
      class MyComplexFormElement extends React.Component {...}
      
      ...
      
      Form.registerElement('complexElement', MyComplexFormElement);
      const testComponent = () => {
        const controls = [{
          element: 'complexElement',
          ... // props to be passed to the MyComplexFormElement
        }]
      
        return ;
      }
      

      Постараюсь добавить, как время появится.

© Habrahabr.ru