简体   繁体   English

具有可变子窗体数量的消化函子(捕捉/抢劫)

[英]Digestive Functors with a variable number of subforms (Snap/Heist)

I'm working on porting a site from PHP to Snap w/ Heist. 我正在将网站从PHP移植到Snap with Heist。 I've ported some of the simpler forms to using Digestive Functors successfully, but now I have to do the tricky ones that require the use of subforms. 我已经将一些简单的表单成功地移植到了消化函子上,但是现在我不得不做一些棘手的表单,这些表单需要使用子表单。

This application manages producing flyers for retail stores, so one of the tasks that need to be done is adding an ad size and defining its physical dimensions on the printed flyer. 该应用程序管理零售商店的传单制作,因此需要完成的任务之一是添加广告尺寸并在打印的传单上定义其物理尺寸。 Sizes will vary depending on the type of page (configurable by the flyer owner) and its orientation (which can only be controlled by the administrators). 大小会因页面类型(由传单所有者配置)及其方向(只能由管理员控制)而有所不同。

表格在PHP版本中是什么样的

This form is guaranteed to have a minimum of 3 cells, most likely going to have 9 cells (as pictured above from the PHP version), but could theoretically have an unlimited number. 保证此表单至少具有3个单元格,最有可能具有9个单元格(如上图所示,从PHP版本开始),但理论上可以有无限数量。

Here's what I've got so far for the dimensions subform: 到目前为止,这是我对Dimensions子窗体的了解:

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)

The form definition doesn't feel quite right (maybe I have completely the wrong idea here?). 表单定义感觉不太正确(也许我在这里完全错了吗?)。 AdDimensions.dimensions should be a Maybe String , since it will be null when coming back from the database when running the query to get a list of all of the possible combinations of size_id/layout_id for a new ad size, but it will be not null from a similar query that will be run when creating the edit form. AdDimensions.dimensions应该是Maybe String ,因为在运行查询以获取新广告尺寸的size_id / layout_id的所有可能组合的列表时从数据库返回时,它将为null,但不为null从创建编辑表单时将运行的类似查询中获取。 The field itself is required ( ad_dimensions.dimensions is set to not null in the database). 该字段本身是必需的(在数据库中ad_dimensions.dimensions设置not null )。

From here, I have no idea where to go to tell the parent form that it has a list of subforms or how I might render them using Heist. 从这里开始,我不知道该去哪里告诉父表单它有一个子表单列表或如何使用Heist渲染它们。

I wrote a special combinator for this quite some time ago for digestive-functors-0.2. 我很早以前为此了一个特殊的combinator,用于digesting-functors-0.2。 It was a very full featured solution that included javascript code allowing fields to be dynamically added and removed. 这是一个功能非常齐全的解决方案 ,其中包括允许动态添加和删除字段的javascript代码 That code was based on a much earlier implementation Chris and I did for the formlets package which digestive-functors eventually superceded. 该代码基于Chris和我为更早实现的Formlet软件包所做的实现,而消化功能最终取代了它。 This function was never ported to work with the new API that digestive-functors got in 0.3. 从来没有将此功能移植到与消化功能在0.3中使用的新API一起使用。

The problem is tricky and has some subtle corner cases, so I would recommend that you spend some time looking at the code. 这个问题很棘手,并且有一些微妙的极端情况,因此我建议您花一些时间看一下代码。 I think Jasper would probably accept a good port of the code into the current version of digestive-functors. 我认为Jasper可能会在当前版本的消化功能中接受大量代码。 It's just that nobody has done the work yet. 只是还没有人完成这项工作。

Edit: This has been done now for the latest digestive-functors. 编辑:现在已经为最新的消化功能器完成了。 See the listOf function. 请参阅listOf函数。

Using the listOf functionality (which wasn't around when the question was originally asked/answered), this is how one would go about about it. 使用listOf功能(最初询问/回答问题时不存在),这就是解决该问题的方法。 This requires 2 forms where the form representing your list's type is a formlet: 这需要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)

Simple forms 简单表格

If you have a simple list of items, digestive-functors-heist defines some splices for this, but you might find that you'll end up with invalid markup, especially if your form is in a table. 如果您有一个简单的项目列表,则digesting-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 . 提供的JavaScript通过digestiveFunctors控制添加和删除从具有一个jQuery依赖性的形式的元素 I ended up writing my own to avoid the jQuery dependency, which is why I'm not using the provided addControl or removeControl splices (attributes for button type elements). 我最终写了我自己的文章以避免jQuery依赖,这就是为什么我不使用提供的addControlremoveControl接头(按钮类型元素的属性)的原因。

Complex Forms 复杂形式

The form in the OP can't make use of the splices provided by digestive-functors-heist because the labels are dynamic (eg. they come from the database) and because we want it in a complex table layout. 由于标签是动态的(例如,它们来自数据库), 并且因为我们希望在复杂的表布局中使用标签,因此OP中的表格无法使用由摘要功能键提供的拼接。 This means we have to perform 2 additional tasks: 这意味着我们必须执行另外两个任务:

Generate the markup manually 手动生成标记

If you haven't looked at the markup that are generated by the digestive-functors-heist splices, you might want to do that first so that you get an idea of exactly what you have to generate so that your Form can be processed correctly. 如果您没有查看由摘要式功能键拼接生成的标记,则可能要先执行该操作,以使您对生成的内容有所了解,以便正确处理表单。

For dynamic forms (eg. forms where the users are allowed to add or remove new items on the fly), you will need a hidden indices field: 对于动态表单(例如,允许用户即时添加或删除新项目的表单),您将需要一个隐藏的索引字段:

<input type='hidden' name='formname.fieldname.indices' value='0,1,2,3' />
  • formname = whatever you named your form when you ran it via runForm formname =通过runForm运行时为表单命名的runForm
  • fieldname = the name of the list field in your main form (adjust as necessary if you're using subforms), in this example it would be named "properties" fieldname =主表单中列表字段的名称(如果使用子表单,请根据需要进行调整),在此示例中,其名称为“ properties”
  • value = a comma delimited list of numbers representing the indices of the subforms that should be processed when the form is submitted 值=逗号分隔的数字列表,代表提交表单时应处理的子表单的索引

When one of the items from your list is removed or a new one is added, this list will need to be adjusted otherwise new items will be completely ignored and removed items will still exist in your list. 当列表中的一项被删除或添加了新项时,将需要调整此列表,否则新项将被完全忽略,并且删除的项仍将存在于列表中。 This step is unnecessary for static forms like the one in the OP. 对于静态形式(如OP中的形式)而言,此步骤是不必要的。


Generating the rest of the form should be pretty straight forward if you already know how to write splices. 如果您已经知道如何编写接头,则生成表格的其余部分应该很简单。 Chunk up the data as appropriate (groupBy, chunksOf, etc.) and send it through your splices. 适当地分块数据(groupBy,chunksOf等),然后通过接头发送。

In case you can't already tell by looking at markup generated by digestive-splices-heist, you'll need to insert the index value of your subform as part of the fields for each subform. 如果您不能通过浏览digesting-splices-heist生成的标记来分辨,则需要插入子表单的索引值作为每个子表单的字段的一部分。 Tthis is what the output HTML should look like for the first field of our list of subforms: 这是子表单列表的第一个字段的输出HTML外观:

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

(Hint: zip your list together with an infinite list starting from 0) (提示:将您的列表与从0开始的无限列表一起压缩)

Pull the data back out of your form when handling errors 处理错误时将数据拉出表单

(I apologize in advance if none of this code is actually able to compile as written, but hopefully it illustrates the process) (如果此代码实际上无法按照编写的方式进行编译,我事先表示歉意,但希望可以说明该过程)

This part is less straight forward than the other part, you'll have to dig through the innards of digestive-functors for this. 这一部分不如其他部分简单明了,因此您必须深入了解消化功能的内在特性。 Basically, we're going to use the same functions digestive-functors-heist does to get the data back out and populate our Thing with it. 基本上,我们将使用digesting-functors-heist所做的相同功能来取回数据并用它填充Thing。 The function we're needing is listSubViews : 我们需要的功能是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

For a static form, this can be as simple as zipping this list together with your list of data. 对于静态表单,这可以像将列表和数据列表一起压缩一样简单。

let x = zipWith (curry updatePropertyData) xs viewList

And then your updatePropertyData function will need to update your records by pulling the information out of the view using the fileInputRead function: 然后,您的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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM