📜 ⬆️ ⬇️

Implement Bootstrap 3 Datepicker in SonataAdminBundle

In this little article I will talk about how to connect a convenient datepicker to the symfony admin panel . By default, the datepicker in SonataAdminBundle looks like this:



And we will turn it into comfortable and beautiful controls:
')


Those who are still suffering with an uncomfortable datepicker, welcome under cat.

If you do not need timing, then you can use a ready-made solution , thanks dmkuznetsov

I will not talk about how to install SonataAdminBundle, you can read about this in this article . I assume that you have already installed the application and admin panel. Well, let's get started.

Datetime field type


The first thing to start with is creating a new form field as described in the documentation . You need to create it in the namespace <vendor_name> \ <bundle_name> \ Form \ Type \, otherwise SensioLabsInsight will swear when testing.

namespace Acme\Bundle\DemoBundle\Form\Type\Field; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\DataTransformerInterface; // Symfony >=2.8 //use Symfony\Component\Form\Extension\Core\Type\TextType; class DateTime extends AbstractType implements DataTransformerInterface { public function buildForm(FormBuilderInterface $builder, array $options) { //           \DateTime $builder->addModelTransformer($this); } public function transform($value) { return $value; //    DataTransformerInterface } public function reverseTransform($value) { //     \DateTime return $value instanceof \DateTime ? $value : new \DateTime($value); } public function buildView(FormView $view, FormInterface $form, array $options) { //       if ($form->getData() instanceof \DateTime) { $view->vars['value'] = $form->getData()->format('Ymd H:i'); } // css   bootstrap  $view->vars['attr']['class'] = 'form-control'; } public function configureOptions(OptionsResolver $resolver) { //   data_class   DataTransformer $resolver->setDefaults([ 'data_class' => \DateTime::class ]); } public function getParent() { // Symfony >=2.8 //return TextType::class; // Symfony <2.8 return 'text'; } public function getName() { return 'datetime'; //     datetime } } 

I did not transfer the standard datetime options to the new class as unnecessary, but you can do it if you need it. In the Time Field Type section, I will describe how to do this using the example of the with_seconds option.

The next item on our program will be the creation of a common form template (themes for forms). In it, we inherit from the Sonata theme and redefine the date pattern. The template is saved in the file Resources / views / Form / fields.html.twig . You can choose another way, but I’m so used to it.

 {% extends 'SonataAdminBundle:Form:form_admin_fields.html.twig' %} {% block datetime_widget %} {% spaceless %} <div class="input-group date form-field-datetime"> <input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"></span> </span> </div> {% endspaceless %} {% endblock datetime_widget %} 

We will need the form-field-datetime class later to hang JavaScript. Now we’ll indicate to Sonata that it needs to use a different theme for the forms by setting the following lines in the config app / config / config.yml :

 sonata_doctrine_orm_admin: templates: form: [ AcmeDemoBundle:Form:fields.html.twig ] 

Do not forget to create a service for a new form field:

  acme.demo.form.type.datetime: class: Acme\Bundle\DemoBundle\Form\Type\Field\DateTime public: false 

We do not create a label for the service as described here because we do not create a new field and we will overload the old one. For the same reason, we do not need it in public access. Now let's rewrite the standard form fields. Create a compiler for the DI container:

 namespace Acme\Bundle\DemoBundle\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; class FormTypePass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $container->setAlias('form.type.datetime', 'acme.demo.form.type.datetime'); } } 

Here we indicate that form.type.datetime is an alias for our newly created acme.demo.form.type.datetime series . Thus, when in the forms we will create a datetime type field, our service will be used. So we change the control without changing the project code. Now connect the compiler to the bundle:

 namespace Acme\Bundle\DemoBundle use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\DependencyInjection\ContainerBuilder; use Acme\BundleDemoBundle\DependencyInjection\Compiler\FormTypePass; class AcmeDemoBundle extends Bundle { public function build(ContainerBuilder $container) { parent::build($container); $container->addCompilerPass(new FormTypePass()); } } 

Now datepicker already has a beautiful and convenient form, it remains only to hang JavaScript to open a drop-down box with a choice of date.



We will install Bootstrap 3 Datepicker which is on packagist.org , for which many thanks to them. Let's write the dependency in composer.json:

 { "require": { … "eonasdan/bootstrap-datetimepicker": "~4.17.37", … } } 

With this package installation it is more convenient to connect it via assetic, which we will do. Register the following lines in app / config / config.yml :

 assetic: assets: admin-js: inputs: - '%kernel.root_dir%/../vendor/eonasdan/bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js' - '@AcmeDemoBundle/Resources/public/js/admin.js' output: js/admin.js sonata_admin: templates: layout: AcmeDemoBundle:Admin:standard_layout.html.twig 

We have defined the js / admin.js file in which our datepicker will build and the JavaScript code that initializes it and hangs it on the appropriate form fields. This file will be located at web / js / admin.js . We also redefined the Sonata layout in order to enable our JavaScript. Let's do this and deal with:

 {% extends 'SonataAdminBundle::standard_layout.html.twig' %} {% block javascripts %} {{ parent() }} <script src="{{ asset('js/admin.js') }}" type="text/javascript"></script> {% endblock %} 

Now we will create the file Resources / public / js / admin.js in which we will bind our form fields with JavaScript.
 $(function(){ $('.form-field-datetime').datetimepicker({ format: 'YYYY-MM-DD HH:mm', locale: 'ru' }); }); 

That's all. Perform assembly assetic and enjoy life:

 app/console assetic:dump web --no-debug 

Date field type


By analogy, we create a date field with a few differences. Class for the form field:

 namespace Acme\Bundle\DemoBundle\Form\Type\Field; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\DataTransformerInterface; // Symfony >=2.8 //use Symfony\Component\Form\Extension\Core\Type\TextType; class Date extends AbstractType implements DataTransformerInterface { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addModelTransformer($this); } public function transform($value) { return $value; } public function reverseTransform($value) { return $value instanceof \DateTime ? $value : new \DateTime($value); } public function buildView(FormView $view, FormInterface $form, array $options) { if ($form->getData() instanceof \DateTime) { $view->vars['value'] = $form->getData()->format('Ym-d'); } $view->vars['attr']['class'] = 'form-control'; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => \DateTime::class ]); } public function getParent() { // Symfony >=2.8 //return TextType::class; // Symfony <2.8 return 'text'; } public function getName() { return 'date'; } } 

Template:

 {% block date_widget %} {% spaceless %} <div class="input-group date form-field-date"> <input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/> <span class="input-group-addon"> <span class="glyphicon glyphicon-calendar"></span> </span> </div> {% endspaceless %} {% endblock date_widget %} 

Service:

  acme.demo.form.type.date: class: Acme\Bundle\DemoBundle\Form\Type\Field\Date public: false 

Add a nickname:

 // .. class FormTypePass implements CompilerPassInterface { public function process(ContainerBuilder $container) { // .. $container->setAlias('form.type.date', 'acme.demo.form.type.date'); } } 

Well, JavaScript:

 $(function(){ // .. $('.form-field-date').datetimepicker({ format: 'YYYY-MM-DD', locale: 'ru' }); }); 

Field type time


By analogy with the previous, but with a few differences. In our project video clips are published and it is necessary to indicate their duration in the admin panel. To do this, we use the time field and set the with_seconds option to true . In the new form field it was necessary to retain this functionality.

 namespace Acme\Bundle\DemoBundle\Form\Type\Field; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\DataTransformerInterface; // Symfony >=2.8 //use Symfony\Component\Form\Extension\Core\Type\TextType; class Time extends AbstractType implements DataTransformerInterface { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addModelTransformer($this); } public function transform($value) { return $value; } public function reverseTransform($value) { return $value instanceof \DateTime ? $value : new \DateTime($value); } public function buildView(FormView $view, FormInterface $form, array $options) { if ($form->getData() instanceof \DateTime) { //     $view->vars['value'] = $form->getData()->format($options['with_seconds'] ? 'H:i:s' : 'H:i'); } $view->vars['attr']['class'] = 'form-control'; //     $view->vars['with_seconds'] = $options['with_seconds']; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => \DateTime::class, 'with_seconds' => false //     ]); } public function getParent() { // Symfony >=2.8 //return TextType::class; // Symfony <2.8 return 'text'; } public function getName() { return 'time'; } } 

Template:

 {% block time_widget %} {% spaceless %} <div class="input-group date form-field-time" data-with-seconds="{{ with_seconds == true ? 1 : 0 }}"> <input type="text" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %}/> <span class="input-group-addon"> <span class="glyphicon glyphicon-time"></span> </span> </div> {% endspaceless %} {% endblock time_widget %} 

Service:

  acme.demo.form.type.time: class: Acme\Bundle\DemoBundle\Form\Type\Field\Time public: false 

Add a nickname:

 // .. class FormTypePass implements CompilerPassInterface { public function process(ContainerBuilder $container) { // .. $container->setAlias('form.type.time', 'acme.demo.form.type.time'); } } 

JavaScript will be a little different:

 $(function(){ // .. $('.form-field-time') .each(function () { var el = $(this), options = {locale: 'ru'}; if (el.data('with-seconds') == 1) { options.format = 'HH:mm:ss'; } else { options.format = 'HH:mm'; } el.datetimepicker(options); }); 

Conclusion


In the place of detention I will say that there is a very interesting ClockPicker library for timing. If it interests you, then you can easily connect it in my examples.

Source: https://habr.com/ru/post/272513/


All Articles