giovedì 23 agosto 2012

Adding a datetime picker with Symfony2


For an application which I'm building with Symfony2 framework I had to include a datetime picker for one of my forms.
Symfony2 comes with a standard date field type which does its job but I found it a little unusable, so I decided to improve it by using jquery and jquery ui javascript toolkit. Jquery UI already provides a datepicker but I also need a time selection tool for my form.. Luckily I found an addon on the internet which does exactly what I'm looking for ;)

Let's start by adding the new form type class in my bundle:

// src/Me/MyBundle/Form/Type/

namespace Me\MyBundle\Form\Type;

use Symfony\Component\Form\AbstractType;

class DateTimePickerType extends AbstractType
{
    public function getDefaultOptions(array $options)
    {
        return array(
            'widget' => 'single_text',
            'format' => 'dd/MM/yyyy HH:mm',
            'attr' => array(
                'autocomplete' => 'off',
                'class' => 'mybundle_datetime_picker',
            ),
        );
    }

    public function getParent()
    {
        return 'date';
    }

    public function getName()
    {
        return 'mybundle_type_datetime_picker';
    }
}

Then we'll need to tell symfony2 how to use the new form type. Let's do this by modifying the service configuration (I'm using YAML notation for this purpose) in my bundle directory:

        services:
          mybundle.form.type.datetimepicker:
            class: "Me\MyBundle\Form\Type\DateTimePickerType"
            tags:
              - { name:"form.type" alias:"mybundle_type_datetime_picker" }
    


Please note that we are extending the date form type (you have to do this in the getParent method) so we dont need to worry about string to date conversion and date format management ;)
The string returned by the getName method is an identifier and must be identical to the one you used in the service configuration (alias field is meant for this) to refer to your form type.

Finally I had to create the template that will be used to render the datetimepicker. By the way, I'm using Twig (the standard in Symfony2) as template engine.
For this purpose I created a new twig template file that will contain my code (and also the javascript instruction to correctly render the picker widget) and I placed under my bundle Resources folder:

{# src/Me/MyBundle/Resources/views/Form/fields.html.twig #}

{% block mybundle_type_datetime_picker_widget %}

    {% javascripts
        'js/tools/jquery-ui-timepicker-addon.js'
        'js/tools/timepicker-local/*'
        output='js/compiled/jquerylib.js'
    %}
<![CDATA[
    
    
    ]]>
    {% endjavascripts %}


    {{ form_widget(form) }}
    
{% endblock %}


A few notes on what we did above:


  • I'm using Assetic to manage my javascript resources (you should too) so you will need to include your bundle in the main Assetic configuration (otherwise your js will not be included in the page and you'll get an error)
  • the name of the block which will contain the widget code is strictly related to the name of your form type (the one returned by the the getName method) and should follow the convention "<name_of_formtype>_widget"
  • you have to tell Twig to look for this block in this file. You can do this by adding a new resource in the Twig configuration in your application
  • we use the form_widget function at the end of our block. This will render the block following the template of the parent form type ("date" in this case)
  • I assume you are using just one date picker per page. If you plan to use more than one, you will have to either move the js in a general file (i.e. your base template) or modify this template to inject the script once in the page


In the end you can finally use your new form type:

// inside an action in your controller
$builder = $this->get('form.factory')->createBuilder();
$builder->add(
    'datetime_var', 'mybundle_type_datetime_picker'
);
$form = $builder->getForm();
// render your template
return array(
  'form' => $form->getView()
);

That's it ;)