JavaScript options : without the given form theme

To manage complex collections, we should store the collection's field name prefix somewhere in the view in order to replace descendent prefixes properly. As this is not really natural to get this information from a view, a form theme is delivered with this jquery plugin. It does the job even if your collection has lots of descendent collections.

But this form theme does nothing more than setting plugin options so if you do not want to use it, that's up to you:

Add, move, remove values and press Submit.
Value
Value
Value

Value :a

Value :b

Value :c


Code used:

    <script type="text/javascript">

        /*
         * Please look at the source code by clicking on the withoutFormTheme.html.twig file above
         */
        $('.form-collection').collection({
            prototype_name: '__name__',
            allow_add: true,
            allow_remove: true,
            name_prefix:  'form[values]'
        });

    </script>
File: Base/BaseController.php
<?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,
        ];
    }
}
File: Base/BaseController.php
File: Controller/OptionsController.php
<?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);
    }
}
File: Controller/OptionsController.php
File: Resources/views/Options/withoutFormTheme.html.twig
{% extends 'FuzAppBundle::layout.html.twig' %}

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

{% block title %}JavaScript options : without the given form theme{% endblock %}

{% block body %}

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

    <p>
        To manage complex collections, we should store the collection's field name prefix somewhere in the view
        in order to replace descendent prefixes properly. As this is not really natural to get this information from
        a view, a form theme is delivered with this jquery plugin. It does the job even if your collection has lots of
        descendent collections.
    </p>

    <p>
        But this form theme does nothing more than setting plugin options so if you do not want to use it, that's
        up to you:
    </p>

    <ul>
        <li>
            <code>name_prefix</code> is mandatory if you do not use the form theme. It should be the prefix of
            all descendant field names. Set this information by using:
            {% verbatim %}{{ form.values.vars.full_name }}{% endverbatim %}.
        </li>
        <li>
            <code>prototype_name</code> should be the same as inside your form type customization. If you
            never given this information, do not set this option (the Symfony2 default value will be used).
        </li>
        <li>
            <code>allow_add</code> should have the same value as the option in your form type. It will enable
            or disable the ability to add elements in the collection.
        </li>
        <li>
            <code>allow_remove</code> should have the same value as the option in your form type. It will enable
            or disable the ability to delete elements from the collection.
        </li>
    </ul>

    {{ form(form) }}

    <hr/>

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

    <hr/>

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

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

{% endblock %}

{% block script %}

    <script type="text/javascript">

        /*
         * Please look at the source code by clicking on the withoutFormTheme.html.twig file above
         */
        $('.form-collection').collection({
            prototype_name: '{{ form.values.vars.prototype.vars.name }}',
            allow_add: true,
            allow_remove: true,
            name_prefix:  '{{ form.values.vars.full_name }}'
        });

    </script>

{% endblock %}
File: Resources/views/Options/withoutFormTheme.html.twig