使用listOf 功能(最初提出/回答问题时不存在),这就是人们将如何处理它的方式。这需要 2 个表单,其中代表列表类型的表单是 formlet:
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)
简单形式
如果您有一个简单的项目列表,那么 digest-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>
There is JavaScript provided by digestiveFunctors to control adding and removing elements from the form that has a jQuery dependency。我最终编写了自己的代码以避免 jQuery 依赖,这就是为什么我不使用提供的 addControl 或 removeControl 拼接(按钮类型元素的属性)。
复杂形式
OP 中的表单无法使用消化函子抢劫提供的拼接,因为标签是动态的(例如,它们来自数据库)并且因为我们想要它复杂的表格布局。这意味着我们必须执行 2 个额外的任务:
手动生成标记
如果您还没有查看由消化-函子-抢劫拼接生成的标记,您可能想先查看一下,以便您了解您必须生成的确切内容,以便您的表单可以正确处理。
对于动态表单(例如,允许用户即时添加或删除新项目的表单),您将需要一个隐藏的 indices 字段:
<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
- formname = 通过
runForm 运行表单时所命名的任何形式
- fieldname = 主表单中列表字段的名称(如果您使用子表单,请根据需要进行调整),在本例中将命名为“properties”
- 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 开始的无限列表一起压缩)
处理错误时将数据从表单中拉回
(如果这些代码实际上都无法按所写的那样编译,我提前道歉,但希望它说明了这个过程)
这部分没有其他部分那么直接,为此您必须深入了解消化功能的内部。基本上,我们将使用 digest-functors-heist 执行的相同功能来获取数据并用它填充我们的事物。我们需要的函数是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