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 ;)

7 commenti:

  1. what if the item is an item of a collection

    how would you approach it?

    RispondiElimina
  2. I'm not sure what you're asking here..
    Could you detail more your question?

    RispondiElimina
  3. Thanks for writing this up! I just ran across the same scenario with the DateTime Picker plugin and this probably saved me a bit of time :)

    RispondiElimina
  4. Hi, the example code will be easier to understand if you replace
    fc_date_picker with mybundle_datetime_picker in the javascript.

    Thanks for this article and keep posting !

    RispondiElimina
  5. thx for the code, but i will have a question. When i set widget option to choice instead of single_text, i cannot see the button but whole calendar instead and it always stays here. Do you have an idea why this can occur?

    RispondiElimina
  6. Great post. I'm new to Symfony2. Please, how could I modify the template to use the picker many times on my page. I'd like to have javascript files included at the bottom of the main page, before the closing html tag, after the jquery and other javascript files already included there. I tried to extend the base.html.twig but it gave no good result. JQueryUi Datepicker uses a stylesheet too. On the other hand, I'm willing to use the widget in SonataAdminBundle forms too. All these things are so challenging for the newbie I am. Thanks

    RispondiElimina
  7. Hi Cristiano, thanks for share it, I've a quest, why you write "<![CDATA[..." structure in template ?

    thanks

    p.s. (t'ho aggiunto tra le amicizie su fb) :)

    RispondiElimina