Symfony表单 - 重新排序/排序CollectionType元素

问题描述:

我正在使用Symfony 2.7创建一个ToDo列表。该Form包含列表名称和简单的文本字段的items集合:Symfony表单 - 重新排序/排序CollectionType元素

class TodoType extends AbstractType { 
    public function getName() { 
     return 'todo_list'; 
    } 

    public function setDefaultOptions(OptionsResolverInterface $resolver) { 
     $resolver->setDefaults(array(
      'data_class' => 'AppBundle\Entity\TodoList', 
     )); 
    } 

    public function buildForm(FormBuilderInterface $builder, array $options) { 
     $builder 
      ->add('name', 'text', array(
       'label' => 'Name', 
       ... 
      )) 
      ->add('create', 'submit', array(
       'label' => 'Save', 
       ... 
      )); 

      $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { 
       $todoList = $event->getData(); 
       $form = $event->getForm(); 

       $form 
        ->add('items', 'collection', array(
         'type' => 'todo_list_item_type', 
         'allow_add' => true, 
         'allow_delete' => true, 
         //'by_reference' => false, 
         'data' => ($todoList != null ? $todoList->getItems() : null), 
        )); 

      }); 
     } 
    } 
} 

这工作得很好。该项目阵列包括在使用“阵列式”的名字形式:

<form ...> 
    ... 
    <ul class="sortable"> 
     <li> 
      <input id="todo_list_items_0_name" class="item-name form-control" type="text" value="Shopping" placeholder="Name" required="required" name="todo_list[items][0][name]"> 
     </li> 
     <li> 
      <input id="todo_list_items_1_name" class="item-name form-control" type="text" value="Wash car" placeholder="Name" required="required" name="todo_list[items][1][name]"> 
     </li> 
     ... 
    <ul> 

现在我想用jQuery Sortable Plugin进行重新排序。这工作正常。用户可以按他喜欢的方式重新排序项目。但是,这不会影响项目的存储顺序。

我认为这个问题是显而易见的:的排序插件只改变DOM,而项目名称中的项目,其中包括他们的数组索引的位置,保持不变。因此,Item0在提交表单时仍然有index = 0,不管它在列表中的新位置。

有没有解决方案来解决这个问题?我发现的唯一解决方案是听取排序插件的stop事件(一旦重新排序完成后触发),并使用一些JavaScript手动查找/替换字段名称中的索引编号。这当然会起作用,但对字段名称的更改会很脆弱。例如,如果未来版本的Symfony将使用some_field_name_0而不是some_field_0_name,则替换代码将不再工作。

另一种解决方案是,将Item类扩展为附加字段sortOrder,并将该信息作为隐藏字段包含在表单中。我仍然需要使用JS手动更新这个索引,但至少我会对格式有更多的控制。

但是我想知道没有更简单的方法。排序集合类型没有什么特别的。所以也许有一个开箱即用的解决方案?

你的方法看起来对我来说合理(尽管我会进一步讨论另一种选择)。没有开箱即用的解决方案,集合表单类型依赖于数据“自然”的顺序。

最后一个问题的解决方案是使用“原型”选项。在您的Twig视图中,您可以显示原型,这意味着集合中任何(新)表单的HTML。
这是如果你使用form_widgetform_row自动完成,否则,你可以做到这一点(从文档here):

<ul class="tags" data-prototype="{{ form_widget(form.tags.vars.prototype)|e('html_attr') }}"> 
    ... 
</ul> 

然后你就可以很容易地,可靠地获取表单名称:

// Get the data-prototype explained earlier 
var prototype = $('.tags').data('prototype'); 

// I assume we use jQuery 
var $prototype = $(prototype); 

// Get the item name 
// by convention, instad of a numeric index it will contain __name__ 
var itemName = $prototype.attr('name'); 

// We can replace '__name__' it to whatever you like 
var nameFor1 = itemName.replace(/__name__/g, 1); 

另一种方法

另一种方法是将position字段添加到todo_list_item元素。假设它是一个Doctrine实体,您可以使用它上面的Sortable Doctrine扩展:您可以在文档中找到所有详细信息here,尽管您不需要自己手动启用Doctrine扩展,但可以使用StofDoctrineExtensionBundle

如果它是一个简单的数据数组(或者您不希望添加扩展名或将位置保存到数据库),则可以从setItems方法中重新排序项目。

+0

谢谢!用重新设置索引号时很容易识别的一些自定义内容替换'__name__'是个不错的选择。我已经使用原型动态地添加新的项目到表单中,我可以在这一步中轻松地更改'__name__'的值。不过,我不明白这是如何工作的项目已经存在,因此与表单加载。我对这些价值/物品没有任何影响。 –

+0

以您找到的解决方案,重新命名字段,以便将正确的顺序“发送”给Symfony,它为您提供了一个生成新名称的模板。因此,如您所述,使您的解决方案免受Symfony升级/更改。 – romaricdrigon