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.


Actions to do after waking up (executed in the given order)
Id
Name
Position
Id
Name
Position
Id
Name
Position
Id
Name
Position

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>
File: Controller/Basic/PositionFieldController.php
<?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,
        ];
    }
}
File: Controller/Basic/PositionFieldController.php
File: Entity/Basic/PositionField/Action.php
<?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;
    }
}
File: Entity/Basic/PositionField/Action.php
File: Entity/Basic/PositionField/Actions.php
<?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;
    }
}
File: Entity/Basic/PositionField/Actions.php
File: Form/Basic/PositionField/ActionType.php
<?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';
    }
}
File: Form/Basic/PositionField/ActionType.php
File: Form/Basic/PositionField/ActionsType.php
<?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';
    }
}
File: Form/Basic/PositionField/ActionsType.php
File: Resources/views/Basic/PositionField/formTheme.html.twig

{# 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 %}
File: Resources/views/Basic/PositionField/formTheme.html.twig
File: Resources/views/Basic/PositionField/positionField.html.twig
{% 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 %}
File: Resources/views/Basic/PositionField/positionField.html.twig