snirgel
snirgel

Reputation: 131

Symfony 2 - rearrange form fields

In our Symfony2 project we have a very complex structure for forms with embedded forms.... Now we got the requirement to bring the output of the form in an specific order.

And here is the problem: We use the form_widget(form) and now we are looking for a solution in the object (e.g. via annotations) or the formbuilder to move a specific field to the end of the form. in symfony 1.4 it was the widget-movefield() function, i guess...

Thx...

Upvotes: 7

Views: 11259

Answers (8)

Luc Hamers
Luc Hamers

Reputation: 167

I had the same problem, but solved it in a different way. Here is my solution, as an idea for others who are searching for this problem.

You could add all the form fields in an event listener, because here you have all the objects data to decide on the fields order. You could for example use a method from the data object to decide on the fields order:

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

Upvotes: 1

Akash Dutta
Akash Dutta

Reputation: 1

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

Upvotes: 0

Henry
Henry

Reputation: 7891

You can do it right in your controller before you render the template:

$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 works as well, however usort creates a non-associative array which will break the form rendering later on.

Upvotes: 0

Damien Ingrand
Damien Ingrand

Reputation: 191

here is the solution I came up with.

I created a class that my types extend.

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';
    }
}

Then you can use it in your inherited classes.

class AddressType extends WfBaseForm
{ 
public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // I add as many fields as I need
        $builder->add( '…');
}

This class extends the one above

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'));
    }
}

Upvotes: 3

Pavel Galaton
Pavel Galaton

Reputation: 593

Had same issue today with the form elements ordering.

Ended up with a trait that will override finishView method and reorder items in children property of a FormView:

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

Then in type implement getFieldsOrder method:

use OrderedTrait;

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

Upvotes: 8

Rafael Dohms
Rafael Dohms

Reputation: 207

You can re-order the fields using this bundle: https://github.com/egeloen/IvoryOrderedFormBundle

This allows you to do things like this:

$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'));

This was going to be in core, but was rejected and pulled out into a bundle.

Upvotes: 10

Somescout
Somescout

Reputation: 33

As I understand, you want to use only form_widget(form) in final template.

Let assume we have two inherited models (ModelA, ModelB) and form types for them (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
}

If you render formB, you will get A,B,C order, but you want A,C,B. To accomplish that, create form template and add reference to app. config file:

#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 %}

Now, when you render formB, you will see desired order and keep code structured. It happens because every widget rendered only once, so if you render it before calling parent block, you will change their order.

Upvotes: 0

Jovan Perovic
Jovan Perovic

Reputation: 20193

There's no need to "reorder" fields. All you need to do is to call form_label and/or form_widget for each field individually. Assuming you use Twig you could, for example, do:

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

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

Upvotes: 3

Related Questions