Créer un type de champ DateTimePicker avec jQuery et Symfony

Share Button

Read the English version

Dans ce post, nous allons expliquer comment créer un super type de champ personnalisé qui utilise le DatePicker de jQuery afin de gérer les saisies de date. Ensuite, nous allons l’étendre afin de créer un type de champ DateTime. Et le meilleur? C’est que ce type de champ sera localisé en fonction de la locale de l’utilisateur.
C’est parti!

Créer un type de champ DatePicker

La première étape est de créer la classe du type de champ :

<?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';
    }
}

Ensuite, vous devez enregistrer votre type de champ dans le conteneur de services :

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

Vous devrez aussi modifier votre fichier de thème de formulaire pour ajouter votre logique spécifique :

{# 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 %}

Vous pouvez maintenant utiliser votre nouveau type de formulaire, comme un type classique :

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

  // ...

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

Lorsque vous affichez votre formulaire dans un template, n’oubliez pas d’utiliser votre thème de formulaire personnalisé.

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

Finalement, incluez jQuery, jQuery UI Datepicker et le(s) fichier(s) de localisation.

Définissez une variable globale dans votre layout, qui contient la locale actuelle de l’utilisateur :

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

Dans un fichier JavaScript, définissez un objet qui prendra en charge la locale et l’initialisation des datepickers.

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

acme = function(){
var locale = "";
return{
initLocale : function(){
      if(global.locale){
        locale = global.locale;
      }
      else{
        // Définit une locale par défaut si celle de l'utilisateur n'est pas gérée
        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();

});

Création d’un datetime picker basé sur le type de champ datepicker

Dans cette seconde partie, nous allons créer un type de champ datetime qui est basé sur le type datepicker que nous venons juste de créer.

La première étape est de définir ce nouveau type de champ en tant que service :

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

et de créer la classe associées :

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

        //permettre la surchage des 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';
    }
}

Enfin, nous devons éditer le fichier form_acme_type.html.twig pour ajouter notre nouveau 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 %}

Utilisez votre nouveau type de champ comme vous le feriez habituellement :

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

Voici à quoi cela ressemble dans un template :

DateTipepicker field type

Share Button

5 thoughts on “Créer un type de champ DateTimePicker avec jQuery et Symfony

  1. Salut ! Merci pour ce super tuto !

    Par contre j’ai un petit souci : quand j’ouvre le formulaire , une fois que le js s’execute, le champ du datepicker est encadré en rouge (à cause du required=”required”) avant même que l’on ne clique où que ce soit…

    • Bonjour et merci.

      Peut être qu’il manque le {% form_rest(form) %} dans le template? Je ne l’ai pas mis dans le post.
      Idem pour le required, essayez d’ajouter $builder->add(‘startDate’,'datePicker’,array(‘required’=>false));

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