Ответ 1
Используя функциональность listOf
(которой не было, когда вопрос был первоначально задан/ответил), так об этом можно было бы поговорить. Для этого требуется 2 формы, где форма, представляющая ваш тип списка, является форматом:
data Thing = Thing { name: Text, properties: [(Text, Text)] }
thingForm :: Monad m => Maybe Thing -> Form Text m Thing
thingForm p = Thing
<$> "name" .: text (name <$> p)
<*> "properties" .: listOf propertyForm (properties <$> p)
propertyForm :: Monad m => Maybe (Text, Text) -> Form Text m (Text, Text)
propertyForm p = ( , )
<$> "name" .: text (fst <$> p)
<*> "value" .: text (snd <$> p)
Простые формы
Если у вас есть простой список элементов, digestive-functors-heist определяет некоторые сращивания для этого, но вы можете обнаружить, что в итоге вы получите недопустимую разметку, особенно если ваша форма находится в таблице.
<label>Name <dfInputText ref="formname" /></label>
<fieldset>
<legend>Properties</legend>
<dfInputList ref="codes"><ul>
<dfListItem><li itemAttrs><dfLabel ref="name">Name <dfInputText ref="name" /></dfLabel>
<dfLabel ref="code">Value <dfInputText ref="value" required /></dfLabel>
<input type="button" name="remove" value="Remove" /></li></dfListItem>
</ul>
<input type="button" name="add" value="Add another property" /></dfInputList>
</fieldset>
Есть JavaScript, предоставленный digestiveFunctors для управления добавлением и удалением элементов из формы, которая имеет зависимость jQuery. Я закончил писать свои собственные, чтобы избежать зависимости jQuery, поэтому я не использую предоставленные addControl
или removeControl
сращения (атрибуты для элементов типа кнопки).
Комплексные формы
Форма в OP не может использовать сращивания, предоставляемые пищеварительными функциями-heist, потому что метки являются динамическими (например, они исходят из базы данных) и потому, что мы хотим, чтобы это было в сложном макете таблицы. Это означает, что мы должны выполнить еще две задачи:
Создать разметку вручную
Если вы не посмотрели на разметку, создаваемую с помощью склеиваний с пищеварением-функтором-heist, вы можете сделать это сначала, чтобы вы поняли, что именно вы должны генерировать, чтобы ваша форма могла быть правильно обработано.
Для динамических форм (например, форм, когда пользователям разрешено добавлять или удалять новые элементы "на лету" ), вам понадобится поле скрытых индексов:
<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
- formname = независимо от того, что вы назвали своей формой, когда вы запустили ее через
runForm
- fieldname = имя поля списка в вашей основной форме (при необходимости отрегулируйте, если вы используете подформы), в этом примере оно будет называться "свойствами"
- value = список номеров с разделителями-запятыми, представляющий индексы подформ, которые должны обрабатываться при отправке формы
Когда один из элементов из вашего списка удален или добавлен новый, этот список нужно будет скорректировать, иначе новые элементы будут полностью проигнорированы, а удаленные элементы все равно будут существовать в вашем списке. Этот шаг не нужен для статических форм, подобных тем, которые существуют в OP.
Формирование остальной части формы должно быть довольно простым, если вы уже знаете, как писать сплайсы. Выделите данные по мере необходимости (groupBy, chunksOf и т.д.) И отправьте их через ваши сращивания.
В случае, если вы уже не можете сказать, просмотрев разметку, сгенерированную методом пищеварения-сращивания, вам нужно вставить значение индекса вашей подформы как часть полей для каждой подформы. Вот как выглядит выходной HTML-код для первого поля нашего списка подформ:
<input type='text' name='formname.properties.0.name' value='Foo' />
<input type='text' name='formname.properties.0.value' value='Bar' />
(Подсказка: запишите свой список вместе с бесконечным списком, начиная с 0)
Извлеките данные из формы при обработке ошибок
(Я извиняюсь заранее, если ни один из этого кода не может скомпилировать, как написано, но, надеюсь, он иллюстрирует процесс)
Эта часть менее прямолинейна, чем другая, вам придется прорыть внутренности пищеварительных функций для этого. В принципе, мы будем использовать те же функции, что и пищеварительные функции-heist, чтобы вернуть данные и заполнить нашу Thing. Нам нужна функция listSubViews
:
-- where `v` is the view returned by `runForm`
-- the return type will be `[View v]`, in our example `v` will be `Text`
viewList = listSubViews "properties" v
Для статической формы это может быть так же просто, как прошивать этот список вместе с вашим списком данных.
let x = zipWith (curry updatePropertyData) xs viewList
И тогда ваша функция updatePropertyData должна будет обновить ваши записи, вытащив информацию из представления с помощью функции fileInputRead
:
updatePropertyData :: (Text, Text) -> View Text -> (Text, Text)
updatePropertyData x v =
let
-- pull the field information we want out of the subview
-- this is a `Maybe Text
val = fieldInputRead "value" v
in
-- update the tuple
maybe x ((fst x, )) val