Symfony 表单 - 重新排序/排序 CollectionType 元素
Symfony form - reorder / sort CollectionType elements
我正在使用 Symfony 2.7 创建待办事项列表。 Form
包含列表名称的简单文本字段和 items
:
的集合
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),
));
});
}
}
}
这很好用。项目数组包含在使用 "array style" 名称的表单中:
<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 重新订购商品。这很好用。用户可以根据自己的喜好重新订购商品。但是,这对项目的存储顺序没有任何影响。
我认为问题很明显: sortable 插件仅更改项目在 DOM
中的位置,而项目名称包括它们的数组索引, 保持不变。因此,当表单提交时,Item0 仍然有 index = 0
,无论它在列表中的新位置如何。
是否有任何内置解决方案来解决这个问题? 目前我找到的唯一解决方案是监听可排序插件的 stop
事件(触发一次重新排序完成)并使用一些 JavaScript 手动 find/replace 字段名称中的索引号。这当然可行,但很容易受到字段名称更改的影响。例如,如果 Symfony 的未来版本将使用 some_field_name_0
而不是 some_field_0_name
,替换代码将不再起作用。
另一种解决方案是,使用附加字段 sortOrder
扩展 Item
class,并将此信息作为隐藏字段包含在表单中。我仍然需要使用 JS 手动更新此索引,但至少我可以更好地控制格式。
但是我想知道是否有更简单的方法。排序集合类型没什么特别的。那么也许有开箱即用的解决方案?
你的方法在我看来是合理的(尽管我将进一步讨论另一种选择)。没有开箱即用的解决方案,收集表单类型依赖于数据 "natural" 顺序。
最后一个问题的解决方案是使用 "prototype" 选项。在您的 Twig 视图中,您可以显示原型,即集合中任何(新)表单的 HTML。
如果您使用 form_widget
或 form_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 in the documentation, though you don't need to manually enable the Doctrine extension by yourself, you can use StofDoctrineExtensionBundle.
如果它是一个简单的数据数组(或者您不想添加扩展名,或者不想将位置保存到数据库),您可以从 setItems
中重新排序项目方法。
我正在使用 Symfony 2.7 创建待办事项列表。 Form
包含列表名称的简单文本字段和 items
:
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),
));
});
}
}
}
这很好用。项目数组包含在使用 "array style" 名称的表单中:
<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 重新订购商品。这很好用。用户可以根据自己的喜好重新订购商品。但是,这对项目的存储顺序没有任何影响。
我认为问题很明显: sortable 插件仅更改项目在 DOM
中的位置,而项目名称包括它们的数组索引, 保持不变。因此,当表单提交时,Item0 仍然有 index = 0
,无论它在列表中的新位置如何。
是否有任何内置解决方案来解决这个问题? 目前我找到的唯一解决方案是监听可排序插件的 stop
事件(触发一次重新排序完成)并使用一些 JavaScript 手动 find/replace 字段名称中的索引号。这当然可行,但很容易受到字段名称更改的影响。例如,如果 Symfony 的未来版本将使用 some_field_name_0
而不是 some_field_0_name
,替换代码将不再起作用。
另一种解决方案是,使用附加字段 sortOrder
扩展 Item
class,并将此信息作为隐藏字段包含在表单中。我仍然需要使用 JS 手动更新此索引,但至少我可以更好地控制格式。
但是我想知道是否有更简单的方法。排序集合类型没什么特别的。那么也许有开箱即用的解决方案?
你的方法在我看来是合理的(尽管我将进一步讨论另一种选择)。没有开箱即用的解决方案,收集表单类型依赖于数据 "natural" 顺序。
最后一个问题的解决方案是使用 "prototype" 选项。在您的 Twig 视图中,您可以显示原型,即集合中任何(新)表单的 HTML。
如果您使用 form_widget
或 form_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 in the documentation, though you don't need to manually enable the Doctrine extension by yourself, you can use StofDoctrineExtensionBundle.
如果它是一个简单的数据数组(或者您不想添加扩展名,或者不想将位置保存到数据库),您可以从 setItems
中重新排序项目方法。