Symfony 2 - 重新排列表单字段

7
在我们的Symfony2项目中,我们有一个非常复杂的表单结构,其中嵌套了表单....现在我们需要按照特定的顺序输出表单内容。
问题在于:我们使用form_widget(form),现在我们正在寻找一种解决方案,可以通过对象(例如通过注释)或formbuilder将特定字段移动到表单的末尾。在Symfony1.4中,它是widget-movefield()函数,我想是这样的...
谢谢...
8个回答

10

您可以使用此捆绑包重新排序字段:https://github.com/egeloen/IvoryOrderedFormBundle

这使您可以执行以下操作:

$builder
->add('g', 'text', array('position' => 'last'))
->add('a', 'text', array('position' => 'first'))
->add('c', 'text')
->add('f', 'text')
->add('e', 'text', array('position' => array('before' => 'f')))
->add('d', 'text', array('position' => array('after' => 'c')))
->add('b', 'text', array('position' => 'first'));

这本来应该是核心功能,但被拒绝了并被移出到另一个包中。


8
今天我遇到了与表单元素排序相关的问题。最终,我写了一个trait,重写了finishView方法,并在FormView的children属性中重新排序了项目。
trait OrderedTrait
{
    abstract function getFieldsOrder();

    public function finishView(FormView $view, FormInterface $form, array $options)
    {
        /** @var FormView[] $fields */
        $fields = [];
        foreach ($this->getFieldsOrder() as $field) {
            if ($view->offsetExists($field)) {
                $fields[$field] = $view->offsetGet($field);
                $view->offsetUnset($field);
            }
        }

        $view->children = $fields + $view->children;

        parent::finishView($view, $form, $options);
    }
}

然后在类型实现中编写getFieldsOrder方法:
use OrderedTrait;

function getFieldsOrder()
{
    return [
        'first',
        'second',
        'next',
        'etc.',
    ];
}

1
这是一个非常好的、干净的解决方案!谢谢! - virtualize
很容易扩展这段代码,以便向由getFieldsOrder返回的字段列表添加权重。如果有人需要此功能,请随时与我联系。 - Pavel Galaton

3

这是我想出的解决方案。

我创建了一个类,让我的类型继承它。

namespace WF\CORE\CoreBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class WfBaseForm extends AbstractType
{

    protected function useFields(FormBuilderInterface $builder, $fields)
    {
        foreach ($builder->all() as $field) {

            if (!in_array($field->getName(), $fields))
                $builder->remove($field->getName());
        }
    }


    public function reorder(FormBuilderInterface $builder, $keys = array())
    {
        $fields         = $builder->all();
        $ordered_fields = array_merge(array_flip($keys), $fields);
        $this->useFields($builder, array());

        foreach($ordered_fields as $field)
            $builder->add($field);

    }

    public function getName()
    {
        return 'base';
    }
}

然后你可以在你继承的类中使用它。
class AddressType extends WfBaseForm
{ 
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // I add as many fields as I need
        $builder->add( '…');
}

这个类继承了上面的那个类

class ModifyAddressType extends BaseType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder->add('title', 'text', array('constraints' => array(new NotBlank())));

        $this->reorder($builder, array('title', 'firstname', 'lastname'));
    }
}

3
不需要“重新排序”字段。您只需要为每个字段单独调用form_label和/或form_widget即可。假设您使用Twig,您可以这样做:
<div>{{ form_label(form.firstName) }}</div>
<div>{{ form_widget(form.firstName) }}</div>

<div>{{ form_label(form.lastName) }}</div>
<div>{{ form_widget(form.lastName) }}</div>

谢谢,但这是不可能的。我们有一个非常复杂的结构,并且需要form_widget(form)函数,因此我们必须在后端完成这个任务! - snirgel
你尝试过按照期望的顺序将字段添加到 FormBuilder 实例中吗? - Jovan Perovic
这是不可能的。我们大约有80个带有依赖关系、继承等的对象类型,所以我们无法手动操作这样的东西... - snirgel
1
好的,我的同事做了这个黑客行为,非常肮脏,但是它有效:{% set label = form_widget(form.orderlabel) %} {% set input = form_widget(form.orderinput) %}{{ form_rest(form) }}{{ label|raw }} {{ label|input }}如果 Symfony 能够防止我们使用此类方法,那就太好了 :D如果有人知道更干净的解决方案,我们会很高兴听到好的答案。 - snirgel

1
我曾经遇到过同样的问题,但是用了不同的方法解决了它。以下是我的解决方案,供其他搜索这个问题的人参考。
你可以将所有表单字段添加到事件监听器中,因为在此处你拥有所有对象数据来决定字段顺序。例如,你可以使用数据对象中的一个方法来决定字段顺序:
public function buildForm(FormBuilderInterface $builder, array $options)
{
    // Don't use $builder->add(), or just for those fields which are always 
    // at the beginning of the form.

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

        // Now add all the fields in the order you need them to be, e.g by reading
        // the needed order from the data entry. 
        if ($entry->isFieldAFirst()) { 
             $form->add(fieldA);
             $form->add(fieldB);
        } else {
            $form->add(fieldB);
            $form->add(fieldA);
        }
    }
}

0

在渲染模板之前,您可以直接在控制器中完成它:

$form = ...

$view = $form->createView();

// Perform standard array operations to reorder: `$view->children`
// Here's a very simple example:

$firstField = $view->children['firstField'];
unset($view->children['firstField']);
$view->children['firstField'] = $firstField;

return array('form' => $view);

uasort 也可以实现相同的功能,但是 usort 创建的非关联数组会在后面破坏表单呈现。


0
{{ form_start(form) }}

    <div>{{ form_label(form.title) }}</div>
    <div>{{ form_widget(form.title,{'id': 'blog_title'} )}}</div>

    <div>{{ form_label(form.tag) }}</div>
    <div>{{ form_widget(form.tag,{'id': 'blog_tag'} )}}</div>

    <div>{{ form_label(form.category) }}</div>
    <div>{{ form_widget(form.category,{'id': 'blog_category'} )}}</div>

    <div>{{ form_label(form.image) }}</div>
    <div>{{ form_widget(form.image,{'id': 'blog_image'} )}}</div>

    <div>{{ form_label(form.body) }}</div>
    <div>{{ form_widget(form.body,{'id': 'editor'} )}}</div>

    <input type="submit" class="btn btn-success" value="Create" />
{{ form_end(form) }}

0

据我理解,您想在最终模板中仅使用form_widget(form)。

假设我们有两个继承的模型(ModelA,ModelB)和它们的表单类型(ModelAType,ModelBType)

class ModelA {
  private $A;
  private $B;

  // Getters and setters
}

class ModelB extends ModelA {
  private $C;

  // Getters and setters
}

/**
 * @DI\Service(id = "form.type.modelA")
 * @DI\Tag("form.type", attributes={ "alias":"model_a_type" })
 */
class FormAType extends AbstractType {
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      ->add('A')
      ->add('B')
    ;
  }

   // getName and so on
}

/**
 * @DI\Service(id = "form.type.modelA")
 * @DI\Tag("form.type", attributes={ "alias":"model_b_type" })
 */
class FormAType extends AbstractType {
  public function getParent() {
    return "model_a_type";
  }

  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      ->add('C')
    ;
  }

   // getName and so on
}

如果你渲染formB,你会得到A、B、C的顺序,但是你想要A、C、B的顺序。为了实现这一点,创建表单模板并在应用程序配置文件中添加引用即可:
#app/config/config.yml
twig:
   ....
    form:
        resources:
            - YourAppBundle:Form:fields.html.twig


{# src/YourAppBundle/Resources/views/Form/fields.html.twig #}
{% block model_a_type_widget %}
   {{ form_widget(form.A) }}
   {{ form_widget(form.B) }}
{% endblock model_a_type_widget %}

{% block model_b_type_widget %}
   {{ form_widget(form.A) }}
   {{ form_widget(form.C) }}

   {{ block('model_a_type_widget') }}
{% endblock model_b_type_widget %}

现在,当您渲染formB时,您将看到所需的顺序并保持代码结构良好。这是因为每个小部件只渲染一次,因此如果在调用父块之前渲染它,则会更改它们的顺序。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接