JavaScript options : drag & drop

If jquery.ui.sortable is available within your application, your form collections will be automatically sortable using drag & drop. Do not hesitate to test this feature on all other forms in this demo website, even on collection of form collections.

As this feature is enabled by default, set drag_drop option to false to disable it (see demo 1 below).

You can play with up and down options to hide them (see demo 2 below).

You can add custom jquery.ui.sortable options by setting them in drag_drop_options option (see demo 3 below). But as start and update sortable options are already used by this plugin, you should use drag_drop_start and drag_drop_update callbacks instead if needed (see demo 4 below).

Drag & drop do not work if allow_move_up or allow_move_down are disabled.

Demo 1: drag & drop disabled

You can't move those form fields using drag & drop.


Value : a

Value : b

Value : c


Code used:

    <script type="text/javascript">

        $('.disabled-collection').collection({
            drag_drop: false
        });

    </script>

Demo 2: hidden move up/down buttons

Just create any invisible container with collection-up and collection-down in your form theme or explicitely display:none those css classes.


Value : a

Value : b

Value : c


Code used:

    <style>
        .nobuttons-collection .collection-up, .nobuttons-collection .collection-down {
            display:none;
        }
    </style>

    <script type="text/javascript">

        $('.nobuttons-collection').collection({
            up: '<div style="display:none;"></div>',
            down: '<div style="display:none;"></div>'
        });

    </script>

Demo 3: customize sortable options

You can give any standard options to jquery.ui.sortable by passing it to the drag_drop_options option.


Value : a

Value : b

Value : c


Code used:

    <script type="text/javascript">

        $('.moreoptions-collection').collection({
            drag_drop_options: {
                revert: true
            }
        });

    </script>

Demo 4: use start/update sortable options

As this plugin uses the start and update sortable callbacks, you should use drag_drop_start and drag_drop_update options instead.

Please drag & drop...

Value : a

Value : b

Value : c


Code used:

    <script type="text/javascript">

        var isDragging = false;
        $('.startupdate-collection').collection({
            drag_drop_options: {
                stop: function (ui, event, elements, element) {
                    if (isDragging) {
                        $('#demo').html('<div class="alert alert-warning">Position unchanged!</div>');
                        isDragging = false;
                    }
                }
            },
            drag_drop_start: function (ui, event, elements, element) {
                $('#demo').html('<div class="alert alert-danger">Dragging....</div>');
                isDragging = true;
            },
            drag_drop_update: function (ui, event, elements, element) {
                $('#demo').html('<div class="alert alert-success">Position updated!</div>');
                isDragging = false;
            }
        });

    </script>

<?php

namespace Fuz\AppBundle\Base;

use Fuz\AppBundle\Entity\Value;
use Fuz\AppBundle\Form\ValueType;
use Fuz\QuickStartBundle\Base\BaseController as QuickStartBase;
use Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\HttpFoundation\Request;

class BaseController extends QuickStartBase
{
    protected function createContextSample(Request $request, $name = 'form', $values = ['a', 'b', 'c'])
    {
        $data = ['values' => $values];

        $form = $this
           ->get('form.factory')
           ->createNamedBuilder($name, Type\FormType::class, $data)
           ->add('values', Type\CollectionType::class, [
               'entry_type'    => Type\TextType::class,
               'label'         => 'Add, move, remove values and press Submit.',
               'entry_options' => [
                   'label' => 'Value',
               ],
               'allow_add'    => true,
               'allow_delete' => true,
               'prototype'    => true,
               'attr'         => [
                   'class' => "{$name}-collection",
               ],
           ])
           ->add('submit', Type\SubmitType::class)
           ->getForm()
        ;

        $form->handleRequest($request);
        if ($form->isValid()) {
            $data = $form->getData();
        }

        return [
            $name         => $form->createView(),
            "{$name}Data" => $data,
        ];
    }

    protected function createAdvancedContextSample(Request $request, $name = 'advancedForm')
    {
        $a = new Value('a');
        $b = new Value('b');
        $c = new Value('c');

        $data = ['values' => [$a, $b, $c]];

        $form = $this
           ->get('form.factory')
           ->createNamedBuilder($name, Type\FormType::class, $data)
           ->add('values', Type\CollectionType::class, [
               'entry_type'   => ValueType::class,
               'label'        => 'Add, move, remove values and press Submit.',
               'allow_add'    => true,
               'allow_delete' => true,
               'prototype'    => true,
               'required'     => false,
               'attr'         => [
                   'class' => "{$name}-collection",
               ],
           ])
           ->add('submit', Type\SubmitType::class)
           ->getForm()
        ;

        $form->handleRequest($request);
        if ($form->isValid()) {
            $data = $form->getData();
        }

        return [
            $name         => $form->createView(),
            "{$name}Data" => $data,
        ];
    }
}
<?php

namespace Fuz\AppBundle\Controller;

use Fuz\AppBundle\Base\BaseController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;

/**
 * @Route("/options")
 */
class OptionsController extends BaseController
{

    /**
     * JavaScript options
     *
     * An overview of almost all options that you
     * can enable/disable/customize on the fly.
     *
     * @Route("/overview", name="overview")
     * @Template()
     */
    public function overviewAction(Request $request)
    {
        return $this->createContextSample($request);
    }

    /**
     * JavaScript options
     *
     * Customized buttons
     *
     * @Route("/customButtons", name="customButtons")
     * @Template()
     */
    public function customButtonsAction(Request $request)
    {
        return $this->createContextSample($request);
    }

    /**
     * JavaScript options
     *
     * Disable buttons you don't want
     *
     * @Route("/enableButtons", name="enableButtons")
     * @Template()
     */
    public function enableButtonsAction(Request $request)
    {
        return array_merge(
           $this->createContextSample($request), $this->createAdvancedContextSample($request)
        );
    }

    /**
     * JavaScript options
     *
     * Control the minimum and maximum of allowed number of elements
     *
     * @Route("/numberCollectionElements", name="numberCollectionElements")
     * @Template()
     */
    public function numberCollectionElementsAction(Request $request)
    {
        return array_merge(
           $this->createContextSample($request), $this->createAdvancedContextSample($request)
        );
    }

    /**
     * JavaScript options
     *
     * Add the button close to each collection elements or only at the bottom
     *
     * @Route("/addButtonAtTheBottom", name="addButtonAtTheBottom")
     * @Template()
     */
    public function addButtonAtTheBottomAction(Request $request)
    {
        return array_merge(
           $this->createContextSample($request, 'enabled'), $this->createContextSample($request, 'disabled')
        );
    }

    /**
     * JavaScript options
     *
     * Run a callback before or after adding, deleting and moving elements
     *
     * @Route("/eventCallbacks", name="eventCallbacks")
     * @Template()
     */
    public function eventCallbacksAction(Request $request)
    {
        return array_merge(
           $this->createContextSample($request, 'eventsBefore'), $this->createContextSample($request, 'eventsAfter')
        );
    }

    /**
     * JavaScript options
     *
     * Use this plugin without the attached form-theme
     *
     * @Route("/withoutFormTheme", name="withoutFormTheme")
     * @Template()
     */
    public function withoutFormThemeAction(Request $request)
    {
        return $this->createContextSample($request);
    }

    /**
     * JavaScript options
     *
     * Initialize a collection with a given minimum number of elements
     *
     * @Route("/givenMinimumElements", name="givenMinimumElements")
     * @Template()
     */
    public function givenMinimumElementsAction(Request $request)
    {
        return $this->createContextSample($request, 'form', []);
    }

    /**
     * JavaScript options
     *
     * Hide move-up on the first item and move-down on the last one
     *
     * @Route("/hideMoveUpDown", name="hideMoveUpDown")
     * @Template()
     */
    public function hideMoveUpDownAction(Request $request)
    {
        return $this->createContextSample($request, 'form');
    }

    /**
     * JavaScript options
     *
     * Drag & Drop allow to get rid of "move up" and "move down" buttons
     *
     * @Route("/dragAndDrop", name="dragAndDrop")
     * @Template()
     */
    public function dragAndDropAction(Request $request)
    {
        return [
            'disabled'    => $this->createAdvancedContextSample($request, 'disabled'),
            'nobuttons'   => $this->createAdvancedContextSample($request, 'nobuttons'),
            'moreoptions' => $this->createAdvancedContextSample($request, 'moreoptions'),
            'startupdate' => $this->createAdvancedContextSample($request, 'startupdate'),
        ];
    }

    /**
     * JavaScript options
     *
     * Run a callback before and after collection initialization
     *
     * @Route("/initCallbacks", name="initCallbacks")
     * @Template()
     */
    public function initCallbacksAction(Request $request)
    {
        return $this->createContextSample($request);
    }

    /**
     * JavaScript options
     *
     * Put buttons to custom locations in your page.
     *
     * @Route(
     *      "/buttons-custom-location",
     *      name = "buttonsCustomLocation"
     * )
     * @Template()
     */
    public function buttonsCustomLocationAction(Request $request)
    {
        return array_merge(
           $this->createContextSample($request, 'collectionA'), $this->createContextSample($request, 'collectionB'), $this->createContextSample($request, 'collectionC')
        );
    }

    /**
     * JavaScript options
     *
     * Enable / disable fade animation when adding / removing
     * collection elements.
     *
     * @Route("/fadeInFadeOut", name="fadeInFadeOut")
     * @Template()
     */
    public function fadeInFadeOutAction(Request $request)
    {
        return $this->createContextSample($request);
    }
}
{% extends 'FuzAppBundle::layout.html.twig' %}

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

{% block title %}JavaScript options : drag & drop{% endblock %}

{% block body %}

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

    <p>
        If <code>jquery.ui.sortable</code> is available within your application, your form collections will be automatically
        sortable using drag & drop. Do not hesitate to test this feature on all other forms in this demo website, even on
        collection of form collections.
    </p>

    <p>As this feature is enabled by default, set <code>drag_drop</code> option to <code>false</code> to disable it (see demo 1 below).</p>

    <p>You can play with <code>up</code> and <code>down</code> options to hide them (see demo 2 below).</p>

    <p>
        You can add custom <code>jquery.ui.sortable</code> options by setting them in <code>drag_drop_options</code> option (see demo 3 below).
        But as <code>start</code> and <code>update</code> sortable options are already used by this plugin, you should use
        <code>drag_drop_start</code> and <code>drag_drop_update</code> callbacks instead if needed (see demo 4 below).
    </p>

    <p>Drag & drop do not work if <code>allow_move_up</code> or <code>allow_move_down</code> are disabled.</p>

    <div class="row">

        {# demo 1: drag & drop disabled #}
        <div class="col-md-6 col-sm-12 col-xs-12">

            <h4>Demo 1: drag & drop disabled</h4>

            <p>
                You can't move those form fields using drag & drop.
            </p>

            {%
                form_theme disabled.disabled
                    'jquery.collection.html.twig'
                    'FuzAppBundle:Options:options-theme.html.twig'
            %}
            {{ form(disabled.disabled) }}

            <hr/>

            {% for value in disabled.disabledData.values %}
                <p>Value : {{ value }}</p>
            {% endfor %}

            <hr/>

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

        </div>

        {# demo 2: hidden move up/down buttons #}
        <div class="col-md-6 col-sm-12 col-xs-12">

            <h4>Demo 2: hidden move up/down buttons</h4>

            <p>
                Just create any invisible container with <code>collection-up</code> and <code>collection-down</code> in
                your form theme or explicitely display:none those css classes.
            </p>

            {%
                form_theme nobuttons.nobuttons
                    'jquery.collection.html.twig'
                    'FuzAppBundle:Options:options-theme.html.twig'
            %}
            {{ form(nobuttons.nobuttons) }}

            <hr/>

            {% for value in nobuttons.nobuttonsData.values %}
                <p>Value : {{ value }}</p>
            {% endfor %}

            <hr/>

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

        {# demo 3: customize sortable options #}
        <div class="col-md-6 col-sm-12 col-xs-12">

            <h4>Demo 3: customize sortable options</h4>

            <p>
                You can give any standard options to <code>jquery.ui.sortable</code> by passing it to
                the <code>drag_drop_options</code> option.
            </p>

            {%
                form_theme moreoptions.moreoptions
                    'jquery.collection.html.twig'
                    'FuzAppBundle:Options:options-theme.html.twig'
            %}
            {{ form(moreoptions.moreoptions) }}

            <hr/>

            {% for value in moreoptions.moreoptionsData.values %}
                <p>Value : {{ value }}</p>
            {% endfor %}

            <hr/>

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

        {# demo 4: use start/update sortable options #}
        <div class="col-md-6 col-sm-12 col-xs-12">

            <h4>Demo 4: use start/update sortable options</h4>

            <p>
                As this plugin uses the <code>start</code> and <code>update</code> sortable callbacks,
                you should use <code>drag_drop_start</code> and <code>drag_drop_update</code> options
                instead.
            </p>

            <div id="demo"><div class="alert alert-warning">Please drag & drop...</div></div>

            {%
                form_theme startupdate.startupdate
                    'jquery.collection.html.twig'
                    'FuzAppBundle:Options:options-theme.html.twig'
            %}
            {{ form(startupdate.startupdate) }}

            <hr/>

            {% for value in startupdate.startupdateData.values %}
                <p>Value : {{ value }}</p>
            {% endfor %}

            <hr/>

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

    </div>

    {{
        tabs([
            'Base/BaseController.php',
            'Controller/OptionsController.php',
            'Resources/views/Options/dragAndDrop.html.twig',
        ])
    }}

{% endblock %}

{% block script %}

{% block script_disabled %}

    <script type="text/javascript">

        $('.disabled-collection').collection({
            drag_drop: false
        });

    </script>

{% endblock %}

{% block script_nobuttons %}

    <style>
        .nobuttons-collection .collection-up, .nobuttons-collection .collection-down {
            display:none;
        }
    </style>

    <script type="text/javascript">

        $('.nobuttons-collection').collection({
            up: '<div style="display:none;"></div>',
            down: '<div style="display:none;"></div>'
        });

    </script>

{% endblock %}

{% block script_moreoptions %}

    <script type="text/javascript">

        $('.moreoptions-collection').collection({
            drag_drop_options: {
                revert: true
            }
        });

    </script>

{% endblock %}

{% block script_startupdate %}

    <script type="text/javascript">

        var isDragging = false;
        $('.startupdate-collection').collection({
            drag_drop_options: {
                stop: function (ui, event, elements, element) {
                    if (isDragging) {
                        $('#demo').html('<div class="alert alert-warning">Position unchanged!</div>');
                        isDragging = false;
                    }
                }
            },
            drag_drop_start: function (ui, event, elements, element) {
                $('#demo').html('<div class="alert alert-danger">Dragging....</div>');
                isDragging = true;
            },
            drag_drop_update: function (ui, event, elements, element) {
                $('#demo').html('<div class="alert alert-success">Position updated!</div>');
                isDragging = false;
            }
        });

    </script>

{% endblock %}
{% endblock %}