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

时间:2012-10-07 13:13:58

标签: haskell haskell-snap-framework heist digestive-functors

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

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

what the form looks like in the PHP version

此表格保证至少有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)

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

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

2 个答案:

答案 0 :(得分:5)

我很久以前为消化函子写了a special combinator for this -0.2。这是一个非常full featured solution,其中包含javascript code,允许动态添加和删除字段。该代码基于Chris和我为formlets包做的早期实现,消化函数最终取代了。这个函数从未被移植到使用消化函数在0.3中获得的新API。

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

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

答案 1 :(得分: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)

简单表格

如果你有一个简单的项目列表,消化函数 - 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依赖,这就是为什么我没有使用提供的addControlremoveControl拼接(按钮类型元素的属性)。

复杂表格

OP中的表单无法利用消化函数提供的拼接 - 因为标签是动态的(例如,它们来自数据库)因为我们想要它复杂的表格布局。这意味着我们必须执行另外两项任务:

手动生成标记

如果您没有查看由消化系统生成的标记 - heist拼接,您可能希望首先执行此操作,以便您准确了解必须生成的内容,以便您的表单可以处理得当。

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

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

如果删除了列表中的某个项目或添加了新项目,则需要调整此列表,否则将完全忽略新项目,并且列表中仍会存在已删除的项目。静态表单(如OP中的静态表单)不需要此步骤。


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

如果你不能通过查看消化 - 拼接 - heist生成的标记来判断,则需要将子表单的索引值作为每个子表单的字段的一部分插入。这就是我们的子表单列表的第一个字段的输出HTML应该是什么样的:

<input type='text' name='formname.properties.0.name' value='Foo' />
<input type='text' name='formname.properties.0.value' value='Bar' />

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

处理错误时将数据从表单中拉回来

(如果这些代码实际上没有按照书面编译进行编译,我提前道歉,但希望它能说明这个过程)

这部分不像其他部分那么直接,你必须挖掘消化函子的内脏。基本上,我们将使用相同的功能消化 - 仿函数 - 抢劫确实将数据退回并用它填充我们的东西。我们需要的功能是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