Войти с помощью github
Форум /

Поиска помощи и фидбека пост.

Работа с формами, обрабатываемыми на клиенте, начала доставлять немало боли, особенно на фоне аналогичного опыта с Angular/Vue.js.

Появилась мысль облегчить решение таких задач. Основная идея была в том, чтобы сделать подход к формам более декларативным. Упростить добавление типового контрола в форму. Сделать возможность легко мапить форму на неплоские модели данных. Ну и это должно более-менее безболезненно встраиваться в существующее приложение (что-нибудь вроде bem-core + bem-components) без тотального переписывания всего и вся.

Прикручивание того же Vue.js в качестве дополнения, (если не замены) i-bem выглядело прикольно, но как-то не зашло, ибо появляются намёки на неконсистентность методологий.

Оставалось одно — свой велосипед. Важное требование — максимально простое решение. И никаких angular-like дайджестов. Идея с сеттерами во Vue ок, но лень всё это писать, можно проще.

В итоге пришёл даже не к какой-то программной единице, решающей проблемы, а, скорее, к подходу к их решению.

Идея в том, чтобы выделить три очевидных составляющих:

  • Контроллер состояний.
  • «Форма».
  • Компоненты «формы».

Контроллер состояний хранит стейты и общается с компонентами. Компоненты — это блоки, непосредственно работающие с данными. Инпут — компонент. Он может и отражать изменения данных, и сам их инициировать. Блок с текстом также может быть компонентом, отображающим какую-то часть стейта. Форма описывает стейт и привязывает компоненты к нему. Причём привязывается на уровне BEMHTML, пробрасывая в компонент имя стейта и путь к нужным данным в нём. Никаких поисков компонентов для установки значений или подписки на их события в форме нет.

В качестве шины событий с данными — каналы (events__channels). Именно каналы. У каждого стейта свой канал. В каждом канале два типа событий: read и write. В идеале компоненты подписываются на первое и генерируют второе. Контроллер состояний — наоборот. В данных события летит путь (dot-separated), по которому произошло изменение и новое значение. Компонент подписывается на все read-события своего стейта. Он сам решает, на какие реагировать, а какие дропать (по пути изменения в стейте). Два типа событий заметно всё упрощают за счёт того, что в общем случае компоненты не общаются между собой. Всё общение между компонентами происходит через контроллер стейтов.

До сих пор это всё дозревало в голове. Наконец удалось выкроить вечер и на коленке проверить жизнеспособность идеи. Всё это вылилось в пока ещё очень сырой контроллер стейтов и пару примеров.

https://github.com/gwer/e-state

В example.blocks есть форма (my-form), обёртка для инпутов (my-input) и составной контрол (editable-list). В данном случае составной контрол сделан проксирующим: не имеет собственного стейта и лишь правильно привязывает свои контролы к стейту внешней формы. Но можно писать и аналогичные контролы с собственным стейтом. Единственное, что следует помнить — если форма имеет несколько экземпляров, привязанных к разным стейтам, имя стейта для такой формы должно быть уникально для каждого экземпляра (например, генерить на основе this.generateId()).

То есть один раз описывается обёртка над инпутом, а дальше для добавления в форму новых полей требуется минимум изменений.

Клиентский JS формы:

eState.init(this.stateName, {
    f1: '1111',
    f2: '2222',
    nested: {
        first: '',
        second: '',
    }
})

Разметка формы:

{
    block: 'my-input',
    js: {
        stateName,
        model: 'f1',
    },
},
{
    block: 'my-input',
    js: {
        stateName,
        model: 'f2',
    },
},
{
    block: 'my-input',
    js: {
        stateName,
        model: 'f2',
    },
},
{
    block: 'my-input',
    js: {
        stateName,
        model: 'nested.first',
    },
},
{
    block: 'my-input',
    js: {
        stateName,
        model: 'nested.second',
    },
},

Можно заметить, что к полю f2 привязываются сразу два инпута. При изменении содержимого одного из них содержимое будет меняться и в другом.

Написать аналогичные обёртки для основных контролов — не должно быть проблемой. Компоненты могут быть более сложными. Можно завязывать их на несколько путей в стейте и вешать на эти пути любую логику. Сейчас примеры представляют собой эдакий аналог ng-model, но можно без проблем сделать, например, аналоги ng-bind, ng-show/ng-hide.

Так вот, к чему всё это. Наверняка кто-то уже подходил к этой проблеме. Есть ли похожие подходы или решения? Какие незамеченные мной проблемы есть у описанного подхода? Может, есть идеи, как это можно улучшить?