npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

kate-form

v1.2.4

Published

kate-form - простой способ управления данными форм и их поведением с разделением логики и отображения для React/Redux.

Downloads

4

Readme

Простое управление сложными интерфейсами на React/Redux

kate-form - простой способ управления данными форм и их поведением с разделением логики и отображения для React/Redux.

Полный пример использования библиотеки можно найти в репозитории kate-form-demo

Ограничения

Библиотека использует объект Proxy. Для браузеров без Proxy используется полифилл, который ограничивает использование this.content теми элементами и их свойствами, которые были определены при создании (kateFormInit или setData('', ...) ).

Браузеры, поддерживающие Proxy можно глянуть тут https://caniuse.com/#search=proxy

Описание

Область использования

При создании сложных интерфейсов на React для:

  • админ. панелей,
  • систем управления задачами/проектами,
  • CRM систем,

и аналогичных, необходимо разрабатывать множество форм.

Каждая форма в подобном интерфесе состоит из данных, элементов которые отображают и изменяют эти данные (input, select, checkbox и прочих), а также логики поведения самих элементов - изменении их атрибутов (видимость, доступность, визуальные характеристики и прочих) в зависимости от данных или действий пользователя.

Например:

  • по клику на кнопку "Отправить уведомление" появляется поле ввода "e-mail"
  • в зависимости от прав пользователя становится доступной кнопка "Редактировать"
  • при отличии значения поля "Пароль" от значения поля "Повторите пароль" выводится надпись "Пароли не совпадают"

Для управления данными формы и их вводом есть, например, популярное решение - redux-form, которое хранит лишь данные формы. При его использовании, все характеристики и логика поведения элементов формы описывается либо в самих элементах либо внутри render. При таком подходе и отображение и логика идут вперемешку. В больших формах со сложной логикой это может приводить к неудобствам как разработки, так и поддержки.

Данная бибилиотека создана для удобства описания поведения форм, с помощью разделения логики и отображения:

  • помимо данных (значения) элемента формы, в redux store хранится и набор параметров, описывающих его визуальные характеристики,
  • за отображение элемента формы отвечает отдельный компонент,
  • предоставляется простой способ манипулирования как данными, так и характеристиками элементов.

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

Рассмотрим два примера, предложенных выше:

  • по клику на кнопку "Отправить уведомление" появляется поле ввода "e-mail" или скрывается, если оно уже отображено
  • при отличии значения поля "Пароль" от значения поля "Повторите пароль" выводится надпись "Пароли не совпадают"

Элементы формы определеяются простым массивом, где мы указыаем их тип и связываем с нужными обработчиками событий:

const elements = [
  {
    type: Elements.BUTTON,
    title: 'Send notification',
    onClick: this.showEMail,
  },
  {
    id: 'email',
    type: Elements.INPUT,
    placeholder: 'e-mail',
    hidden: true,
  },
  {
    id: 'password',
    type: Elements.INPUT,
    placeholder: 'Password',
    inputType: 'password',
    onChange: this.checkPasswords,
  },
  {
    id: 'password2',
    type: Elements.INPUT,
    placeholder: 'Retype password',
    inputType: 'password',
    onChange: this.checkPasswords,
  },
  {
    id: 'passwordsMatchText',
    type: Elements.LABEL,
    title: 'Passwords match',
  },
];

А обработчики определяем как методы компонента и используем в них поле content для доступа к свойствам элементов формы, используя заданные в списке id элементов как имена полей.

showEMail = () => {
  this.content.email.hidden = !this.content.email.hidden;
}
checkPasswords = () => {
  if (this.content.password.value !== this.content.password2.value) {
    this.content.passwordsMatchText.title = 'Passwords do not match';
  } else {
    this.content.passwordsMatchText.title = 'Passwords match';
  }
}

Просто, быстро, удобно и читаемо!

При таком подходе возникает вопрос о расположении различных элементов на странице. В примере выше - они описаны простым массивом, а значит будут выводится единообразно, друг за другом, что, в некоторых случаях недостаточно.

Для решения этого вопроса можно сделать вспомогательные элементы, которые будут регулировать расположение других элементов на станице.

Код обработчиков остается таким-же, т.к. content рекурсивно находит нужные элементы по id.

const elements = [
  {
    type: Elements.GROUP,
    layout: 'horizontal',
    elements: [
      {
        type: Elements.BUTTON,
        title: 'Send notification',
        onClick: this.showEMail,
      },
      {
        id: 'email',
        type: Elements.INPUT,
        placeholder: 'e-mail',
        hidden: true,
      },
    ],
  },
  {
    type: Elements.GROUP,
    elements: [
      {
        id: 'password2_1',
        type: Elements.INPUT,
        placeholder: 'Password',
        inputType: 'password',
        onChange: this.checkPasswords,
      },
      {
        id: 'password2_2',
        type: Elements.INPUT,
        placeholder: 'Retype password',
        inputType: 'password',
        onChange: this.checkPasswords,
      },
      {
        id: 'passwordsMatchText',
        type: Elements.LABEL,
        title: 'Passwords match',
      },
    ],
  },
];

Концепция

Принцип работы kate-form очень простой:

  • есть набор компонентов - компонентов элементов, которые используются в формах, с уникальным именами для каждого
  • в самой форме определяется набор элементов, с указанием имени нужного компонента в поле type
  • Компонент KateForm выполняет рендер элементов, подставляя нужный компонент в соответствии с типом, передавая значение остальных полей как props.

Таким образом достигается разделение отображения и логики:

  • за отображение отвечают компоненты,
  • за логику формы отвечают набор элементов и их взаимодействие в классе компонента формы.

Работа с библиотекой

Полный пример использования библиотеки можно найти в репозитории https://github.com/romannep/kate-form-demo

Установка

npm install kate-form --save

Подключение приложения

Для работы kate-form требуется redux.

Для хранения состояния необходимо подключить reducer из kate-form в корневом reducer-e:

import { reducer } from 'kate-form';

const rootReducer = combineReducers({
  ...
  'kate-form': reducer,
  ...
});

Набор компонентов передается через KateFormProvider в корневом для использующих их форм компоненте.

import { KateFormProvider } from 'kate-form';

  ...
    <KateFormProvider components={components} t={t} [logRerender]>
      <App />
    </KateFormProvider>
  ...

Без передачи компонентов kate-form будет использовать свой минимальный набор встроенных.

Помимо компонентов в KateFormProvider передается функция перевода t которая будет доступна внутри каждого компонента. Без указания функции будет использована функция по умолчанию, просто возвращающая полученный параметр.

const t = param => param;

Для отладки производительности можно указать опциональный параметр logRerender. При этом в console будут выводится сообщения о рендере каждого элемента.

Подключение компонента формы

Компонент формы необходимо подключить к kate-form, используя HOC функцию withKateForm(FormComponent, formPath, subElementsPath= 'elements', kateFormPath = 'kate-form'), где

  • FormComponent - подключаемый компонент
  • formPath - путь к данным формы в redux store относительно всех данных kate-form
  • subElementsPath - имя поля вложенных элементов в групповых компонентах. Необязательный параметр, по умолчанию 'elements'.
  • kateFormPath - путь к данным kate-form в redux store. Необязательный параметр, по умолчанию 'kate-form'
import { withKateForm } from 'kate-form';

const kateFormPath = 'formLayout';

class FormLayout extends Component {

  ...

}

export default withKateForm(FormLayout, kateFormPath);

Фукнция withKateForm передает в props следующие параметры:

  • kateFormInit(formElements) - метод первоначальной установки элементов формы
  • kateFormContent - объект content для работы с элементами формы
  • setData(path, value) - метод прямого изменения данных формы, где path - строка - путь к данным - индексы массива, имена полей объектов через .
  • data - данные формы
  • setValues(obj)- метод, для каждого { key: data } в переданом в параметре объекта ищет элемент с id == key и устанавливает поле value равному data
  • getValues() - метод, который переберает все элементы формы имеющие поле value и возвращает объект где ключами будут id элементов, а значениями - значения полей value
  • kateFormPath - полученный в параметре путь к форме

При первоначальном определении данных формы их необходимо установить в redux store. Для этого используется метод kateFormInit.

Для удобства работы с данными формы объект kateFormContent можно сохранить в свойство класса

Для вывода формы в render необходимо использовать компонент KateForm с параметром path - пути к данным формы относительно всех данных kate-form

import { ..., KateForm } from 'kate-form';

class FormLayout extends Component {
  constructor(props) {
    super(props);
    const { kateFormInit, kateFormContent } = this.props;

    const elements = [
        {
          type: 'button',
          title: 'Show/hide email',
          onClick: this.showEMail,
        },
        {
          id: 'email',
          type: 'input',
          placeholder: 'e-mail',
          hidden: true,
        },

      ...
    ];

    kateFormInit(elements);
    this.content = kateFormContent;
  }

  showEMail = () => {
    this.content.email.hidden = !this.content.email.hidden;
  }

  ...

  render() {
    const { kateFormPath } = this.props;
    return (
      <KateForm path={kateFormPath} />
    );
  }
}

Жизненный цикл

Работать с объектом content для доступа к элементам формы прямо в конструкторе не получится: метод kateFormInit устанавливает элементы формы условно в следующем цикле событий javascript (см детали реализации redux). Отследить момент инициализации данных, а следовательно момент начала возможной работы с content можно с помощью метода react компонента componentDidUpdate. В общем случае (при изменении state родительских компонентов) этот метод может быть вызван не один раз, поэтому логично дополнить компонент функцией shouldComponentUpdate.

shouldComponentUpdate(nextProps) {
  return this.props.data !== nextProps.data;
}
componentDidUpdate() {
  // do some after init stuff
}

При обновлении данных корневого элемента будет естественно перерисована и этот компонент - т.е. метод componentDidUpdate будет вызан повторно. При неохоимости выолнить некоторые действия только разово, можно прибегнуть к setTimeout:

constructor() {
  ...
  ...
  setTimeout(() => {
    // do somethng with content
  }, 0);
}

Компоненты

Компоненты это набор React компонентов, которые представляют собой элементы формы, которые используются для рендера.

const label = ({ title, ...props }) => (
  <span {...props}>{title}</span>
);

const components = {
  ...

  'label': label,
}

В форме мы можем использовать данный копмонент следующим образом:

constructor(props) {
  ...

  const elements = [
    ...

    {
      type: 'label',
      title: 'Some label',
      style: { color: '#FF0000'}
    }
  ];

  ...
}

kate-form выполняет рендер компонента по его имени, указанном в поле type, передавая остальные поля как props. Данный пример будет эквивалентен

<span style={{ color: '#FF0000' }} >Some label</span>

В props компонента, помимо данных элемента передаются еще следующие параметры:

  • setData(subPath, value) - метод изменения данных, где subPath - путь к данным относительно самого элемента и value - значение, которое нужно установить.
  • path - полный путь к самому элементу.
  • t - функция для переводов

Компонент для поля ввода в минимальном ввиде может выглядеть так:

const input = ({ setData, value, ...props }) => {
  const change = (e) => {
    setData('value', e.target.value);
  };
  return (
    <input onChange={change} value={value || ''} {...props} />
  );
};

Компонент для вывода группы элементов, где элементы группы находятся в поле elements

const elements = [
  ...
  {
    type: 'group',
    elements: [
      {
        type: 'button',
        title: 'Send notification',
        onClick: this.showEMail,
      },
      {
        id: 'email',
        type: 'input',
        placeholder: 'e-mail',
        hidden: true,
      },

    ]
  }
  ...
]

в минимальном ввиде может выглядеть так

const group = ({ path, elements, ...props }) => {
  return (
    <div>
      {
        elements.map((item, index) => (
          <div key={index}>
            <KateForm path={`${path}.elements.${index}`} />
          </div>
        ))
      }
    </div>
  );
};

Для исключения использования строк в качестве идентификаторов компонентов, а также для исключения кофликтов имен при использовании разных наборов компонентов логично использовать набор констант:

Компоненты:

const label = ({ title, setData, t, ...props }) => (
  <span {...props}>{t(title)}</span>
);

const Elements = {
  LABEL: Symbol('label'),
  ...
};

const components = {
  [Elements.LABEL]: label,
  ...
};

Форма

import { ..., Elements } from 'kate-form';

...
constructor(props) {
  ...

  const elements = [
    ...

    {
      type: Elements.LABEL,
      title: 'Some label',
      style: { color: '#FF0000'}
    }
  ];

  ...
}

Детали устройства библиотеки

Компонент формы

Данные каждой формы подключаются в redux по определенному пути относительно всех данных kate-form, чтобы можно было использовать несколько различных форм.

Для доступа к данным формы и методу их изменения компонент формы подключается к redux следующим образом

import { getSetData, ... } from 'kate-form';

const kateFormPath = 'formLayout';

class FormLayout extends Component {
  ...

}

const mapStateToProps = (state) => {
  return {
    ...

    data: state['kate-form'][kateFormPath],
  };
};

export default connect(mapStateToProps, {
  ...

  setData: getSetData(kateFormPath),
})(FormLayout);

При таком подключении в props в поле data у нас актуальное состояние данных формы, а в поле setData - метод изменения данных.

В чистом виде, метод для изменения данных принимает путь к данным и значние для установки. Путь - path - строка, с именами полей объекта или индексами массива через ..

Для примера в описании, установка в объекте с id == email поля hidden равным false вызов этого метода будет иметь вид:

setData('0.elements.1.hidden', false)

Структура элементов может быть сложной, со множеством уровней вложенности, поэтому для удобства kate-form предоставляет объект content, где можно удобно обратитья к нужному элементу по его id:

content.email.hidden = false

Данные формы и логика

При первоначальном определении данных формы их необходимо установить в redux store, т.к. kate-form для рендера берет данные от туда. Для этого используется метод setData;

Если компонент предоставляет собой только форму, это можно сделать в методах constructor или componentWillMount.

constructor(props) {
  super(props);
  const { setData } = this.props;

  const elements = [
    ...
  ];

  setData('',elements);
}

Для получения объекта content для удобной работы с элементами формы используется функция getContent. Фнукция создает Proxy объекты для доступа к данным, поэтому ей необходимо передать два метода - получения и установки данных.

import { ..., createContent } from 'kate-form';

...

class FormLayout extends Component {

  constructor(props) {
    super(props);
    const { setData } = this.props;

    ...

    this.content = createContent(this.getData, setData);
  }
  getData = () => this.props.data;

  ...

}

Функция getContent подразумевает, что в элементах - группах их вложенные элементы хранятся в массиве в поле elements. Если вложенные элементы хранятся в поле с другим названием, его необходимо передать третьим параметром.

this.content = createContent(this.getData, setData, 'subElements');

Для обработки значений формы можно использовать объект-массив data, а также функцию getValues, которая переберет все элементы формы имеющие поле value и вернет объект где ключем будет id элемента, а значением - значение его поля value

import { ..., getValues } from 'kate-form';

...

class FormLayout extends Component {
  ...

  logValues = () => {
    console.log(getValues(this.props.data));
  }

}

Функция getValues подразумевает, что в элементах - группах их вложенные элементы хранятся в массиве в поле elements. Если вложенные элементы хранятся в поле с другим названием, его необходимо передать вторым параметром.

getValues(this.props.data, 'subElements')

Рендер формы

Для рендера формы используется компонент KateForm, которому в качестве параметра передается путь к redux store относительно данных kate-form по которому находятся данные формы.

import { ..., KateForm } from 'kate-form';

const kateFormPath = 'formLayout';

...

class FormLayout extends Component {

  ...

  render() {
    return (
      <KateForm path={kateFormPath} />
    );
  }

}

Компонент KateForm работает следующим образом:

  • если по переданному path находится массив, вызывается рендер KateForm для каждого элемента массива с добавлением в path его индекса
  • если по переданному path находится объект, вызывается рендер компонента согласно значению поля type.

Лицензия

MIT