【问题标题】:Digestive Functors with a variable number of subforms (Snap/Heist)具有可变数量子形式的消化函子(Snap/Heist)
【发布时间】:2012-09-27 23:04:48
【问题描述】:

我正在将一个站点从 PHP 移植到 Snap w/Heist。我已经成功地移植了一些更简单的表单来使用消化函子,但现在我必须做一些需要使用子表单的棘手表单。

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

保证此表单至少有 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,因为在运行查询以获取新广告尺寸的 size_id/layout_id 的所有可能组合的列表时从数据库返回时它将为空,但它不会创建编辑表单时将运行的类似查询中的 null 。该字段本身是必需的(ad_dimensions.dimensions 在数据库中设置为not null)。

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

【问题讨论】:

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


    【解决方案1】:

    使用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 依赖,这就是为什么我不使用提供的 addControlremoveControl 拼接(按钮类型元素的属性)。

    复杂形式

    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
    

    【讨论】:

      【解决方案2】:

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

      这个问题很棘手,并且有一些微妙的极端情况,所以我建议您花一些时间查看代码。我认为 Jasper 可能会接受将代码很好地移植到当前版本的消化函子中。只是还没有人完成这项工作。

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

      【讨论】:

      • listOf 函数仅适用于 Formlets,我正在寻找表单列表。在我的例子中,单独的尺寸是没有用的。我需要知道它们的方向和页面类型。由于页面类型和方向的列表是动态的,我不能只相信任何一个集合都不会在用户加载页面到提交之前发生变化。
      • 我倾向于认为应该可以让当前的 listOf 函数为您工作。但如果没有,那么您可能必须做一些自定义的事情。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      • 2011-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多