具有可变数量子窗体的消化函数(Snap / Heist) [英] Digestive Functors with a variable number of subforms (Snap/Heist)

查看:122
本文介绍了具有可变数量子窗体的消化函数(Snap / Heist)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将一个站点从PHP移植到Snap / Heist。我已经将一些简单的表单移植到成功使用消化类函数,但现在我必须做那些需要使用子表单的技巧。



这个应用程序管理生产因此需要完成的任务之一是在印刷的宣传单上添加广告尺寸并定义其实际尺寸。尺寸会因页面类型(传单所有者可配置)及其方向(只能由管理员控制)而异。 https://i.stack.imgur.com/FD7I1.gifalt =PHP版本中的表单看起来像什么>



此表格保证至少有3个单元格,最有可能会有9个单元格(如上图所示的PHP版本),但理论上可以有一个无限制的数目。



这是我到目前为止,我们已经获得了维度子表单:

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

adDimensionsForm :: Monad m => AdDimensions - >表单文本m AdDimensions
adDimensionsForm d = AdDimensions
< $> size_id。:stringRead必须是一个数字(Just $ sizeId d)
< *> layout_id。:stringRead必须是数字(Just $ layoutId d)
*< *> dimensions。:opionalString(维度d)

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



从这里开始,我不知道该怎么去告诉父表单它有一个子表单列表或者我可以如何使用Heist来呈现它们。
<使用 listOf 功能(当问题最初被询问/回答时并没有出现),这个是如何去做的。这需要2个表单,其中表示您的列表类型的表单是一个表单:

  data Thing = Thing {name:Text,properties: [(Text,Text)]} 

thingForm :: Monad m =>也许事情 - >表单文本文件
thingForm p = Thing
<$> name:text(name< $> p)
< *> properties。:listOf propertyForm(properties< $> p)

propertyForm :: Monad m =>也许(文本,文本) - >表单文本m(文本,文本)
propertyForm p =(,)
< $> name:text(fst <$> p)
< *> value。:text(snd< $> p)



简单形式



如果你有一个简单的项目列表,消化函数 - heist为此定义了一些拼接,但是你可能会发现你最终会得到无效的标记,特别是如果你的表单是

 < label>名称< dfInputText ref =formname/>< / label> 

< fieldset>
<图例>属性< /图例>

< dfInputList ref =codes>< ul>
< dfListItem>< li itemAttrs>< dfLabel ref =name>名称< dfInputText ref =name/>< / dfLabel>
< dfLabel ref =code>值< dfInputText ref =valuerequired />< / dfLabel>
< input type =buttonname =removevalue =Remove/>< / li>< / dfListItem>
< / ul>

< input type =buttonname =addvalue =Add another property/>< / dfInputList>
< / fieldset>

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



复杂表单



OP中的表单不能利用由消化仿函数提供的拼接 - 抢劫,因为标签是动态的(例如它们来自数据库),因为我们希望它在复杂的表格布局中。这意味着我们必须执行2个额外的任务:

手动生成标记



如果您还没有看过在由消化仿函数生成的标记中,您可能首先要这样做,以便您确切了解必须生成的内容,以便可以正确处理表单。



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

 < 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开始的无限列表一起压缩)



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



(如果这些代码实际上无法编译,我会提前道歉如书面材料,但希望它能说明这个过程)



这部分不如其他部分那么直截了当,你必须深入挖掘消化函数的内部,这个。基本上,我们将使用相同的功能消化函数 - 海斯特将数据取出并用它填充我们的Thing。我们需要的函数是 listSubViews

   - 其中` v`是`runForm`返回的视图
- 返回类型是`[View v]`,在我们的例子中`v`将是`Text`
viewList = listSubViewspropertiesv

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

  let x = zipWith(curry updatePropertyData)xs viewList 

然后你的updatePropertyData函数需要通过使用 fileInputRead 函数从视图中提取信息来更新记录:

  updatePropertyData ::(Text,Text) - >查看文字 - > (Text,Text)
updatePropertyData xv =
let
- 将我们想要的字段信息从子视图中取出
- 这是一个`Maybe Text
val = fieldInputReadvaluev
in
- 更新元组
可能x((fst x,))val


I'm working on porting a site from PHP to Snap w/ 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).

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.

Here's what I've got so far for the dimensions subform:

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. The field itself is required (ad_dimensions.dimensions is set to not null in the database).

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.

解决方案

Using the listOf functionality (which wasn't around when the question was originally asked/answered), this is how one would go about about it. This requires 2 forms where the form representing your list's type is a 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)

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.

<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. 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).

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. 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
  • 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"
  • 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.


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.

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. Tthis is what the output HTML should look like for the first field of our list of subforms:

<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)

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. The function we're needing is 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 :: (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

这篇关于具有可变数量子窗体的消化函数(Snap / Heist)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆