Category Archives: JavaScript

Symfony Forms and Bootstrap Datetimepicker

In this example I’m going to use the spanish locale and the “Europe/Madrid” timezone.

First we need an entity that will hold the DateTime, for example a Booking entity:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="bookings")
 */
class Booking
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $date;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->date = new \DateTime();
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set date
     *
     * @param \Datetime $date
     * @return Booking
     */
    public function setDate($date)
    {
        $this->date = $date;

        return $this;
    }

    /**
     * Get date
     *
     * @return \Datetime 
     */
    public function getDate()
    {
        return $this->date;
    }
}

Our entity uses a DateTime object but the field in the form is going to be an input that can only have text. That’s why we need a DataTransformer. It will transform a DateTime object to string and vice versa when needed:

<?php

namespace AppBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class DateTimeTransformer implements DataTransformerInterface
{
    /**
     * Transforms an object (DateTime) to a string.
     *
     * @param  DateTime|null $datetime
     * @return string
     */
    public function transform($datetime)
    {
        if (null === $datetime) {
            return '';
        }

        return $datetime->format('d/m/Y H:i');
    }

    /**
     * Transforms a string to an object (DateTime).
     *
     * @param  string $datetime
     * @return DateTime|null
     */
    public function reverseTransform($datetime)
    {
        // datetime optional
        if (!$datetime) {
            return;
        }

        return date_create_from_format('d/m/Y H:i', $datetime, new \DateTimeZone('Europe/Madrid'));
    }
}

Now the form class. You can see how addModelTransformer is used to link the form date field with the DataTransformer. When defining the date field it’s important to render it as a text field and use the attr option to add a data-provide and a data-format that will be added in the HTML input field as attributes. Also some CSS classes are needed (form-control input-inline datetimepicker), you can add them with attr here or later in the Twig template. I’ll show both but only one of them is needed.

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use AppBundle\Form\DataTransformer\DateTimeTransformer;

class BookingFormType extends AbstractType
{
    private $class;

    /**
     * @param string $class The Booking class name
     */
    public function __construct($class)
    {
        $this->class = $class;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('date', 'text', array(
                'required' => true,
                'label' => 'form.label.datetime',
                'translation_domain' => 'AppBundle',
                'attr' => array(
                    'class' => 'form-control input-inline datetimepicker',
                    'data-provide' => 'datepicker',
                    'data-format' => 'dd-mm-yyyy HH:ii',
                ),
            ))
            ->add('submit', 'submit')
        ;

        $builder->get('date')
            ->addModelTransformer(new DateTimeTransformer());
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            array(
                'data_class' => $this->class,
                'intention'  => 'edit',
            )
        );
    }

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

Let’s register the form as a service:

services:
    app.form.type.booking_edit:
        class: AppBundle\Form\Type\BookingFormType
        arguments: [AppBundle\Entity\Booking]
        tags:
            - { name: form.type, alias: booking_edit }

In the template we’ll include the form and the needed JavaScript and CSS for jQuery, MomentJS, Bootstrap and Bootstrap Datetimepicker. Remember that MomentJS must be loaded BEFORE Bootstrap Datetimepicker. Also just before the body closing tag we have to call the datetimepicker┬ámethod on the input field. It’s very important to add “useCurrent: false” if you want to load a datetime as we do in this example.

<html>
    <head>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker-standalone.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
        <title>Symfony and Bootstrap Datetimepicker</title>
    </head>
    <body>
        <div class="container">
            <div class="starter-template">
                <h1>Symfony and Bootstrap Datetimepicker</h1>
                <div class="row">
                    {{ form_start(form) }}
                        {{ form_errors(form) }}
                        <div class="form-group">
                            {{ form_label(form.date, null, { 'label_attr': {'class': 'col-sm-2 control-label'} }) }}{{ form_errors(form.date) }}
                            <div class='input-group date' id='datetimepicker'>
                                {{ form_widget(form.date, {'attr': {'class': 'form-control input-inline datetimepicker'}}) }}
                                <span class="input-group-addon">
                                    <span class="glyphicon glyphicon-calendar"></span>
                                </span>
                            </div>
                        </div>
                        <div class="form-group">
                            {{ form_row(form.submit, { 'label': 'OK' }) }}
                        </div>
                        {{ form_end(form) }}
                    </div>
                </div>
            </div>
        </div>

        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment-with-locales.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script>
        <script type="text/javascript">
            $('#datetimepicker').datetimepicker({
                locale: 'es',
                useCurrent: false,
                sideBySide: true
            });
        </script>
    </body>
</html>

The controller is very simple (the handling of the form is not shown):

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use AppBundle\Entity\Booking;
use AppBundle\Form\Type\BookingFormType;

class MainController extends Controller
{
    /**
     * @Route("/booking/edit/{id}", name="booking_edit")
     * @ParamConverter("id", class="AppBundle:Booking")
     * @param Request $request The Request object
     * @param Booking $booking The Booking object
     * @return Response A Response instance
     */
    public function formAction(Request $request, Booking $booking)
    {
        $form = $this->createForm(new BookingFormType('\AppBundle\Entity\Booking'), $booking);

        $form->handleRequest($request);

        if ($form->isValid()) {
            ...
        }

        return $this->render('form.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}

symfony-bootstrap-datetimepicker

Ref: https://eonasdan.github.io/bootstrap-datetimepicker/
http://ajaxray.com/blog/symfony2-forms-bootstrap-3-datepicker-for-date-field
http://symfony.com/doc/current/reference/forms/types/datetime.html
http://symfony.com/doc/current/cookbook/form/data_transformers.html

Attach event to dinamically added HTML elements with jQuery

If you have an event handler defined like this in JavaScript:

$("a[product-id]").click(edit_product);

function edit_product(event) {
    ...
}

After adding elements (nodes) dinamically (after an AJAX call for example) that fit that selector to the HTML DOM with .append() or .html(), the event won’t work on them. That’s because the element was added AFTER calling the event method that links to the function we want to execute.

To ensure all elements that fit the selector will trigger the event you have to use .on() for example with $(document) as the ancestor:

$(document).on('click', "a[product-id]", edit_product);

jquery-logo

Ref: http://stackoverflow.com/questions/203198/event-binding-on-dynamically-created-elements