mgr-forms-react: Простой компонент для простейших форм
Вы когда нибудь считали, сколько форм вы делаете во время разработки веб-приложения? И я не говорю о сложных формах вроде кастомного date-picker’а или же чего-то сложнее, а простых форм с тремя input, двумя select и одним textarea?
Я не считал. Но когда я начал писать очередное приложение на React и мне за один вечер пришлось создать 5 разных форм — мне поплохело. Ну, а когда разработчику плохеет — разработчик пишет велосипед!
Из таких вот соображений на свет появилась пока еще сырая, но уже используемая мной в двух разных проектах, библиотека для создания простейших форм на 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 ; }
Постараюсь добавить, как время появится.