Store elements position in a field

Use this option when you have a position field in each elements of your collection.

This is very useful when each element of your collection has its own database relationships.



ID Name Position
42 walk the dog 0
43 eat breakfast 1
44 take a shower 2
45 yawn loudly 3

Code used:

    <script type="text/javascript">

        $('.actions-collection').collection({
            position_field_selector: '.my-position',
            allow_duplicate: true
        });

    </script>

<?php

namespace Fuz\AppBundle\Controller\Basic;

use Fuz\AppBundle\Base\BaseController;
use Fuz\AppBundle\Entity\Basic\PositionField\Action;
use Fuz\AppBundle\Entity\Basic\PositionField\Actions;
use Fuz\AppBundle\Form\Basic\PositionField\ActionsType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;

/**
 * @Route("/basic")
 */
class PositionFieldController extends BaseController
{
    /**
     * Storing the element position on a field of your entity.
     *
     * @Route("/positionField", name="positionField")
     * @Template()
     */
    public function positionFieldAction(Request $request)
    {
        $actions = new Actions();
        for ($i = 0; $i <= 3; $i++) {
            $action = new Action();
            $action->setId(42 + $i); // just to distinguish id and position

            switch ($i) {
                case 0:
                    $action->setName('walk the dog');
                    break;
                case 1:
                    $action->setName('eat breakfast');
                    break;
                case 2:
                    $action->setName('take a shower');
                    break;
                case 3:
                    $action->setName('yawn loudly');
                    break;
            }

            $action->setPosition($i);

            $actions->getActions()->add($action);
        }

        $form = $this->createForm(ActionsType::class, $actions);
        if ($request->isMethod('POST')) {
            $form->handleRequest($request);
        }

        return [
            'form' => $form->createView(),
            'data' => $actions,
        ];
    }
}
<?php

namespace Fuz\AppBundle\Entity\Basic\PositionField;

class Action
{
    private $id;
    private $name;
    private $position;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    public function getPosition()
    {
        return $this->position;
    }

    public function setPosition($position)
    {
        $this->position = $position;

        return $this;
    }
}
<?php

namespace Fuz\AppBundle\Entity\Basic\PositionField;

use Doctrine\Common\Collections\ArrayCollection;

class Actions
{
    protected $actions;

    public function __construct()
    {
        $this->actions = new ArrayCollection();
    }

    public function getActions()
    {
        return $this->actions;
    }
}
<?php

namespace Fuz\AppBundle\Form\Basic\PositionField;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ActionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // you'd not expose id normally, but you need to see that id
        // won't change, only position will be updated
        $builder->add('id', TextType::class, [
            'attr' => [
                'readonly' => true,
                'autocomplete' => 'off',
            ],
        ]);

        $builder->add('name', TextType::class, [
            'attr' => [
                'autocomplete' => 'off',
            ]
        ]);

        // position would usually be stored in a hidden type, but here we want
        // you to see the result explicitely
        $builder->add('position', TextType::class, [
            'attr' => [
                'readonly' => true,
                'class'    => 'my-position', // selector is the one used on the js side
                'autocomplete' => 'off',
            ],
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'Fuz\AppBundle\Entity\Basic\PositionField\Action',
        ]);
    }

    public function getBlockPrefix()
    {
        return 'ActionType';
    }
}
<?php

namespace Fuz\AppBundle\Form\Basic\PositionField;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ActionsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('actions', CollectionType::class, [
            'label'        => 'Actions to do after waking up (executed in the given order)',
            'entry_type'   => ActionType::class,
            'allow_add'    => true,
            'allow_delete' => true,
            'prototype'    => true,
            'required'     => false,
            'by_reference' => true,
            'delete_empty' => true,
            'attr'         => [
                'class' => 'actions-collection',
            ],
        ]);

        $builder->add('save', SubmitType::class, [
                'label' => 'See actions',
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'Fuz\AppBundle\Entity\Basic\PositionField\Actions',
        ]);
    }

    public function getBlockPrefix()
    {
        return 'ActionsType';
    }
}

{# remove any label above each element #}
{% block ActionType_label %}
{% endblock %}

{# better disposition of each elements #}
{% block ActionType_widget %}

    <div class="row horizontal-form">
        <div class="col-md-2">
            {{ form_row(form.id) }}
        </div>
        <div class="col-md-3">
            {{ form_row(form.name) }}
        </div>
        <div class="col-md-3">
            {{ form_row(form.position) }}
        </div>
        <div class="col-md-4">
            <div class="form-group">
            <a href="#" class="collection-up btn btn-default" title="Move element up"><span class="glyphicon glyphicon-arrow-up"></span></a>
            <a href="#" class="collection-down btn btn-default" title="Move element down"><span class="glyphicon glyphicon-arrow-down"></span></a>
            <a href="#" class="collection-remove btn btn-default" title="Delete element"><span class="glyphicon glyphicon-trash"></span></a>
            <a href="#" class="collection-add btn btn-default" title="Add element"><span class="glyphicon glyphicon-plus-sign"></span></a>
            <a href="#" class="collection-duplicate btn btn-default" title="Duplicate element"><span class="glyphicon glyphicon-th-large"></span></a>
            </div>
        </div>
    </div>

{% endblock %}
{% extends 'FuzAppBundle::layout.html.twig' %}

{% block extra_js %}
    <script src="{{ asset('js/jquery.collection.js') }}"></script>
{% endblock %}

{% block title %}Store elements position in a field{% endblock %}

{% block body %}

    <h2>{{ block('title') }}</h2>

    <p>Use this option when you have a position field in each elements of your collection.</p>

    <p>This is very useful when each element of your collection has its own database relationships.</p>

    <hr/>

    {%
        form_theme form
            'jquery.collection.html.twig'
            'FuzAppBundle:Basic/PositionField:formTheme.html.twig'
    %}

    {{ form(form) }}

    <hr/>

    <table class="table">
        <thead>
            <th>ID</th>
            <th>Name</th>
            <th>Position</th>
        </thead>
        <tbody>
            {% for action in data.actions %}
                <tr>
                    <td>{{ action.id }}</td>
                    <td>{{ action.name }}</td>
                    <td>{{ action.position }}</td>
                </tr>
            {% else %}
                <tr>
                    <td colspan="3">No actions submitted</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

    <hr/>

    <p>Code used:</p>
    <pre>{{ block('script') | e }}</pre>

    {{
        tabs([
            'Controller/Basic/PositionFieldController.php',
            'Entity/Basic/PositionField/Actions.php',
            'Entity/Basic/PositionField/Action.php',
            'Form/Basic/PositionField/ActionsType.php',
            'Form/Basic/PositionField/ActionType.php',
            'Resources/views/Basic/PositionField/positionField.html.twig',
            'Resources/views/Basic/PositionField/formTheme.html.twig',
        ])
    }}

{% endblock %}

{% block script %}

    <script type="text/javascript">

        $('.actions-collection').collection({
            position_field_selector: '.my-position',
            allow_duplicate: true
        });

    </script>

{% endblock %}