Symfony2 Redactor Form Type


Redactor is a very nice WYSIWYG editor. In this post I will show you how to create a redactor form type. Let’s get into the code.

First up we need to create our RedactorType class.

<?php
// src/Acme/AppBundle/Form/Extension/Type/RedactorType.php
namespace Acme\AppBundle\Form\Extension\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RedactorType extends AbstractType
{

    /**
        * Builds the form view.
        *
        * This method is called for each type in the hierarchy starting form the
        * top most type. Type extensions can further modify the view.
        *
        * A view of a form is built before the views of the child forms are built.
        * This means that you cannot access child views in this method. If you need
        * to do so, move your logic to {@link finishView()} instead.
        *
        * @see FormTypeExtensionInterface::buildView()
        *
        * @param FormView $view    The view
        * @param FormInterface     $form    The form
        * @param array             $options The options
        */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $settings = null;
        if (0 !== count($options['options'])) {
            $settings = json_encode($options['options']);
        }
        $view->vars['redactor_settings'] = $settings;
    }

    /**
        * Sets the default options for this type.
        *
        * @param OptionsResolverInterface $resolver The resolver for the options.
        */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        // @see http://redactorjs.com/docs/settings
        $resolver->setDefaults(array(
            'options' => array(),
        ));
    }

    /**
        * Returns the name of the parent type.
        *
        * You can also return a type instance from this method, although doing so
        * is discouraged because it leads to a performance penalty. The support
        * for returning type instances may be dropped from future releases.
        *
        * @return string|null|FormTypeInterface The name of the parent type if any, null otherwise.
        */
    public function getParent()
    {
        return 'textarea';
    }

    /**
        * Returns the name of this type.
        *
        * @return string The name of this type
        */
    public function getName()
    {
        return 'redactor';
    }

}

I have left the docblocks in that can be found in the source code.

In the method getDefaultOptions we need to setup the options. In this case, we just define one and the default is an empty array.

NOTE We could have defined this as settings or something other than options. We could also set the default value to null and refactor the code a little.

Since Redactor comes with tons of options, we don’t want to define each and everyone with the default. The way it’s setup we can use this in a form to pass all the redactor options to the javascript.

We also need to make sure those options are put into a JavaScript object so we will json_encode the array to get a string. We will use this when making the twig template.

Next up we need to register the class and let the framework know that we have a new form type.

<!-- src/Acme/AppBundle/Resources/config/services.xml -->
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="form.type.redactor" class="Acme\AppBundle\Form\Extension\Type\RedactorType">
            <tag name="form.type" alias="redactor" />
        </service>
    </services>

</container>

We need to create a form template to use with our new form type.

{# src/Acme/AppBundle/Resources/views/Form/fields.html.twig #}
{% block redactor_widget %}
    {% spaceless %}
        <link rel="stylesheet" href="{{ asset('bundles/acmeapp/css/redactor.css') }}" />
        {{ block('textarea_widget') }}
        <script src="{{ asset('bundles/acmeapp/js/redactor.js') }}"></script>
        <script type="text/javascript">
            $(document).ready(function(){
                $('#{{ id }}').redactor({{ form.vars.redactor_settings|raw }});
            });
        </script>
    {% endspaceless %}
{% endblock redactor_widget %}

NOTE Make sure you have redactor.css located in the directory src/Acme/AppBundle/Resources/public/css and redactor.js in src/Acme/AppBundle/Resources/public/js directory. You can always have these in your global css/js folders. Also make sure you include jQuery above the form.

Notice that we need to filter form.vars.redactor_settings. Symfony2 will escape the output and break the functionality we want.

One more file to let the framework know to use the above block.

# app/config/config.yml
# Twig Configuration
twig:
    form:
        resources:
            - 'AcmeAppBundle:Form:fields.html.twig'

That’s all there is to it. Now start using it in your forms.

<?php
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('body', 'redactor', array(
            'required' => true,
            'attr'     => array(
                'rows' => 35,
            ),
            'options' => array(
                'autoresize' => false,
                'buttons'    => array('bold', 'italic', 'image', 'link', 'video')
            ),
        ));
}

References and Resources

  • http://redactorjs.com/
  • http://symfony.com/doc/master/reference/dic_tags.html#form-type
  • http://symfony.com/doc/master/cookbook/form/create_custom_field_type.html