具有可变数量子表格的消化函数(Snap/Heist)

cim*_*non 10 haskell haskell-snap-framework heist digestive-functors

我正在努力将一个站点从PHP移植到Snap w/Heist.我已经将一些更简单的形式移植到成功使用Digestive Functors,但现在我必须做一些需要使用子表单的棘手的形式.

此应用程序管理为零售商店生产传单,因此需要完成的任务之一是添加广告尺寸并在打印的传单上定义其物理尺寸.尺寸将根据页面类型(可由传单所有者配置)及其方向(只能由管理员控制)而有所不同.

PHP版本中的表单是什么样的

这种形式保证至少有3个单元格,最有可能有9个单元格(如上图所示的PHP版本),但理论上可以有无限数量.

这是我到目前为止维度子表单的内容:

data AdDimensions = AdDimensions
    { sizeId :: Int64
    , layoutId :: Int64
    , dimensions :: Maybe String
    }

adDimensionsForm :: Monad m => AdDimensions -> Form Text m AdDimensions
adDimensionsForm d = AdDimensions
    <$> "size_id" .: stringRead "Must be a number" (Just $ sizeId d)
    <*> "layout_id" .: stringRead "Must be a number" (Just $ layoutId d)
    <*> "dimensions" .: opionalString (dimensions d)
Run Code Online (Sandbox Code Playgroud)

表单定义感觉不太正确(也许我在这里完全错误的想法?). AdDimensions.dimensions应该是a Maybe String,因为在运行查询时从数据库返回时它将为null,以获取新广告大小的size_id/layout_id的所有可能组合的列表,但是从类似查询中它不会为null将在创建编辑表单时运行.字段本身是必需的(在数据库中ad_dimensions.dimensions设置not null).

从这里开始,我不知道去哪里告诉父表单它有一个子表单列表或者我如何使用Heist渲染它们.

mig*_*yte 5

很久以前,我为消化函子-0.2 写了一个特殊的组合器.这是一个非常全功能的解决方案,其中包含允许动态添加和删除字段的JavaScript代码.该代码基于Chris和我为formlets包做的早期实现,消化函数最终取代了.这个函数从未被移植到消化函数得到0.3的新API.

问题很棘手并且有一些微妙的角落情况,所以我建议你花一些时间查看代码.我认为Jasper可能会在当前版本的消化函数中接受一个好的代码端口.只是没有人完成这项工作.

编辑:现在已经完成了最新的消化函数.请参阅listOf函数.


cim*_*non 2

使用该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)
Run Code Online (Sandbox Code Playgroud)

简单的形式

如果您有一个简单的项目列表,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>
Run Code Online (Sandbox Code Playgroud)

DigestiveFunctors提供了JavaScript来控制从具有 jQuery 依赖项的表单中添加和删除元素。我最终编写了自己的以避免 jQuery 依赖性,这就是为什么我不使用提供的addControlremoveControl拼接(按钮类型元素的属性)。

复杂的形式

OP中的表单无法使用digestive-functors-heist提供的拼接,因为标签是动态的(例如,它们来自数据库)并且因为我们希望它处于复杂的表格布局中。这意味着我们必须执行另外 2 项任务:

手动生成标记

如果您还没有查看由digestion-functors-heist 拼接生成的标记,您可能需要首先执行此操作,以便准确了解必须生成的内容,以便正确处理您的表单。

对于动态表单(例如,允许用户动态添加或删除新项目的表单),您将需要一个隐藏的索引字段:

<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
Run Code Online (Sandbox Code Playgroud)
  • formname = 您在运行表单时为表单命名的任何内容runForm
  • fieldname = 主表单中列表字段的名称(如果您使用子表单,则根据需要进行调整),在此示例中,它将被命名为“properties”
  • value = 以逗号分隔的数字列表,表示提交表单时应处理的子表单的索引

当列表中的一项被删除或添加新一项时,需要调整此列表,否则新项目将被完全忽略,而删除的项目仍将存在于您的列表中。对于像 OP 中那样的静态表单,此步骤是不必要的。


如果您已经知道如何编写拼接,则生成表单的其余部分应该非常简单。根据需要对数据进行分块(groupBy、chunksOf 等)并通过拼接发送。

如果您无法通过查看digestive-splices-heist 生成的标记来判断,则需要插入子表单的索引值作为每个子表单字段的一部分。对于子表单列表的第一个字段,输出 HTML 应该是这样的:

<input type='text' name='formname.properties.0.name' value='Foo' />
<input type='text' name='formname.properties.0.value' value='Bar' />
Run Code Online (Sandbox Code Playgroud)

(提示:将列表与从 0 开始的无限列表压缩在一起)

处理错误时从表单中提取数据

(如果这些代码实际上都无法按编写的方式编译,我提前表示歉意,但希望它说明了该过程)

这部分不像其他部分那么直接,为此你必须深入挖掘消化函数的内部结构。基本上,我们将使用与digestive-functors-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
Run Code Online (Sandbox Code Playgroud)

对于静态表单,这可以像将此列表与数据列表压缩在一起一样简单。

let x = zipWith (curry updatePropertyData) xs viewList
Run Code Online (Sandbox Code Playgroud)

然后您的 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
Run Code Online (Sandbox Code Playgroud)