DateTimePicker field type with Symfony and jQuery

Share Button

Lire la version française

In this third post, we’ll explain how to create a great custom field type that uses jQuery DatePicker to handle date input, and then, how to extend it to create a datetime field type. And the best? The datepicker is localized according to the user’s locale.
Let’s start.

Create a datepicker field type

The first step is to create the field type class :

<?php

namespace Acme\AcmeDemoBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class DatePickerType extends AbstractType
{
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'widget' => 'single_text'
        ));
    }
    public function getParent()
    {
        return 'date';
    }

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

Then, you have to create your field type as a service :

#Acme/AcmeDemoBundle/Resources/config/services.yml
services:
  form.type.datePicker:
    class: Acme\AcmeDemoBundle\Form\Type\DatePickerType
    tags:
      - { name: form.type, alias: datePicker }

You also need to custom the form theme file to add your specific logic :

{# Acme\AcmeDemoBundle\Resources\views\Form\form_acme_type.html.twig #}
{% block datePicker_widget %}
    {% spaceless %}
        <input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/>
        {%  set attr = attr|merge({'class': ((attr.class|default('') ~ ' acmeDatePicker')|trim)}) %}
        {% set  id = id ~ "_datepicker" %}
        {% set  full_name = "datepicker_" ~ full_name  %}
        <input type="text" {{ block('widget_attributes') }}/>
    {% endspaceless %}
{% endblock datePicker_widget %}

Now you can use your new field type in a form, as a classic type :

// Acme/AcmeDemoBundle/Form/Type/DemoType.php

  // ...

  $builder->add('startDate','datePicker',array());

When you render your form in a template, do not forget to use your custom form theme.

{# Acme/AcmeDemoBundle/Resources/views/tripEdit.html.twig #}
{% form_theme form 'AcmeDemoBundle:Form:form_acme_type.html.twig' %}
[...]
{{  form_row(form.startDate}) }}

Finally, include jQuery, jQuery UI Datepicker and the localization file(s).

Define a global variable in your layout, that contains the current user’s locale :

<script>
        global = {
            locale   : '{{ app.request.locale }}'
        }
</script>

In some js file, define an object that will handle the locale and the datepickers initialization.

// Acme\AcmeDemoBundle\Resources\public\js\file.js

acme = function(){
var locale = "";
return{
initLocale : function(){
      if(global.locale){
        locale = global.locale;
      }
      else{
        //Set a default locale if the user's one is not managed
        console.error('The locale is missing, default locale will be set (fr_FR)');
        locale = "fr_FR";
      }
},
getLocale : function(length){
      if(length == 2){
          return locale.split('_')[0];
      }
      return locale;
},
initDatePicker : function(){

    if($.datepicker.regional[acme.getLocale(4)] != undefined ){
        $.datepicker.setDefaults( $.datepicker.regional[acme.getLocale(4)] );
    }else if($.datepicker.regional[acme.getLocale(2)] != undefined){
        $.datepicker.setDefaults( $.datepicker.regional[acme.getLocale(2) ] );
    }else{
        $.datepicker.setDefaults( $.datepicker.regional['']);
    }

    $('.acmeDatePicker').each(function(){
        var id_input=this.id.split('_datepicker')[0];
        var sfInput = $('#'+id_input)[0];
        if(! (sfInput)){
            console.error('An error has occurred while creating the datepicker');
        }
        $(this).datepicker({
            'yearRange':$(this).data('yearrange'),
            'changeMonth':$(this).data('changemonth'),
            'changeYear':$(this).data('changeyear'),
            'altField' : '#'+id_input,
            'altFormat' : 'yy-mm-dd',
            'minDate' : null,
            'maxDate': null
        });

        $(this).keyup(function(e) {
            if(e.keyCode == 8 || e.keyCode == 46) {
                $.datepicker._clearDate(this);
                $('#'+id_input)[0].value = '';
            }
        });
        var dateSf = $.datepicker.parseDate('yy-mm-dd',sfInput.value);

        $(this).datepicker('setDate',dateSf);
        $(this).show();
        $(sfInput).hide();
    })
}
}}()


$(document).ready(function(){
    acme.initLocale();
    acme.initDatePicker();

});

Create a datetime picker based on datepicker field type

In this second part, we will create a datetime field type, based on the datepicker type we have just created before.

The first step is to define this new field type as a service :

#Acme/AcmeDemoBundle/Resources/config/services.yml
form.type.dateTimePicker:
  class: Acme\AcmeBundle\Form\Type\DateTimePickerType
  tags:
   - { name: form.type, alias: dateTimePicker }

And to create the Type class associated :

<?php

namespace Acme\AcmeBundle\Form\Type;

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

class DateTimePickerType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $dateOptions = $builder->get('date')->getOptions();
        
        //override options
        array_merge($options, $dateOptions);
        
        $builder->remove('date')
                ->add('date', 'datePicker', $dateOptions);
    }
 
    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return 'datetime';
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'date_widget' => 'single_text',
            'time_widget' => 'choice'
        ));
    }
    
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'dateTimePicker';
    }
}

Finally, we have to edit the form_acme_type.html.twig file to add our new widget :

{# Acme/AcmeDemoBundle/Resources/views/Form/form_acme_type.html.twig  #}
{% block dateTimePicker_widget %}
    {% set attr = attr|merge({'class': (attr.class|default('') ~ ' acmeDateTimePicker')|trim}) %}
    
{{ form_errors(form.date) }} {{ form_errors(form.time) }} {{ form_widget(form.date, {'id' : id ~ '_date'}) }} {{ form_widget(form.time, {'id' : id ~ '_time'}) }}
{% endblock %}

Use your new field type as you would do normally:

// Acme/AcmeDemoBundle/Form/Type/DemoType.php
  
  // ..
  $builder->add('startDate','dateTimePicker',array());

Here is what it’s look like in a template :

DateTipepicker field type

Share Button

8 thoughts on “DateTimePicker field type with Symfony and jQuery

  1. Awesome post. It worked perfectly. However I have a question. How can this datetimepicker be used in SonataAdminBundle forms ?

  2. It worked in SonataAdminBundle, with no additional action on my part. Thanks so much

  3. Your example would be prefect without those 2 typos:

    - #Acme/AcmeDemoBundle/Resources/config/services.yml
    you wrote “service” it’s not “service” but “services”

    - the small caret “-” is not the same: if people copy/paste it won’t work: there’s a difference between “−” and “-” (yes yes)!

    Congratulations for this nice tutorial.

  4. Another small correction: in // Acme\AcmeDemoBundle\Resources\public\js\file.js

    You declare acme = function(){}.
    So, basically, when you need acme, you should call it as a function; with “()”.

    But in your sample you do:

    acme.initLocale();
    acme.initDatePicker();

    This should be something like :

    var a = acme();
    a.initLocale();
    a.initDatePicker();

    Or something like that.

  5. We execute directly the created function :
    acme = function(){[...]}()

    We tried to simplify our production code but indeed some useless code still remains.

    Thanks a lot for reading and for your corrections, we will update this post very soon! :)

  6. etoileweb it worked for you but not working for me, i am getting this error
    InvalidArgumentException: A “tags” entry is missing a “name” key for service “form.type.datePicker” in services.yml.
    did you get something like this ?

    I followed the same documentation. Any idea ?

    Thanks :)

  7. Done it, it was a typo mistake in Article , in services.yml just read some comment above and fixed it.

    And now the error is

    Could not load type “datePicker”

    And what can be the reason ?

  8. Hello, you fixed your first error by fixing your service configuration.
    Once again, the error is quite explicit : your datePickerType (your form) is unknown as a service. This can be :
    - another error in your service configuration
    - the service alias is not the same as the form name (see the getName() method)
    - your service file is not load
    - maybe something else

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Protected by WP Anti Spam