Permalink
Browse files

feature #239 add a custom form field type example (yceruto)

This PR was merged into the master branch.

Discussion
----------

add a custom form field type example

**The first purpose** of this PR is to provide a practical example of how to create a custom form field type.

**The second purpose** is to improve the user interface to the field "Published At" on edit a Post,
using a JavaScript date library like bootstrap-datetimepicker for parsing, validating, manipulating, and formatting dates. http://eonasdan.github.io/bootstrap-datetimepicker.

>**Important:** Is not the purpose of this PR create a custom form field type to support all options and features of the datetimepicker plugin.

References:
* http://symfony.com/doc/current/cookbook/create_custom_field_type.html
* http://symfony.com/doc/current/best_practices/forms.html#custom-form-field-types

Main Topics:
* Defining the custom form Field Type.
* Creating your form Field Type as a Service.
* Creating a Template for the custom form field Type
* Configuring the custom form template.
* Initializing datetime picker JavaScript plugin.
* Using the form Field Type into PostType form.

Others Topics (refactor):
* Moving the initialization of the highligth JavaScript plugin for `main.js` file.

**Preview:**
![datetimepicker](https://cloud.githubusercontent.com/assets/2028198/10723013/d1930664-7b8c-11e5-9409-ec8b369a1c2f.png)

Commits
-------

d8376ea add a custom form field type example
2 parents b4f82a7 + d8376ea commit 4dd93aabe6c8459ecc0046971d6b0415e71a0618 @javiereguiluz javiereguiluz committed Oct 30, 2015
@@ -0,0 +1,5 @@
+/*!
+ * Datetimepicker for Bootstrap 3
+ * version : 4.17.37
+ * https://github.com/Eonasdan/bootstrap-datetimepicker/
+ */.bootstrap-datetimepicker-widget{list-style:none}.bootstrap-datetimepicker-widget.dropdown-menu{margin:2px 0;padding:4px;width:19em}@media (min-width:768px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:992px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}@media (min-width:1200px){.bootstrap-datetimepicker-widget.dropdown-menu.timepicker-sbs{width:38em}}.bootstrap-datetimepicker-widget.dropdown-menu:before,.bootstrap-datetimepicker-widget.dropdown-menu:after{content:'';display:inline-block;position:absolute}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:before{border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,0.2);top:-7px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after{border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid white;top:-6px;left:8px}.bootstrap-datetimepicker-widget.dropdown-menu.top:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #ccc;border-top-color:rgba(0,0,0,0.2);bottom:-7px;left:6px}.bootstrap-datetimepicker-widget.dropdown-menu.top:after{border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid white;bottom:-6px;left:7px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:before{left:auto;right:6px}.bootstrap-datetimepicker-widget.dropdown-menu.pull-right:after{left:auto;right:7px}.bootstrap-datetimepicker-widget .list-unstyled{margin:0}.bootstrap-datetimepicker-widget a[data-action]{padding:6px 0}.bootstrap-datetimepicker-widget a[data-action]:active{box-shadow:none}.bootstrap-datetimepicker-widget .timepicker-hour,.bootstrap-datetimepicker-widget .timepicker-minute,.bootstrap-datetimepicker-widget .timepicker-second{width:54px;font-weight:bold;font-size:1.2em;margin:0}.bootstrap-datetimepicker-widget button[data-action]{padding:6px}.bootstrap-datetimepicker-widget .btn[data-action="incrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Hours"}.bootstrap-datetimepicker-widget .btn[data-action="incrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Increment Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="decrementHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Hours"}.bootstrap-datetimepicker-widget .btn[data-action="decrementMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Decrement Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="showHours"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Hours"}.bootstrap-datetimepicker-widget .btn[data-action="showMinutes"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Show Minutes"}.bootstrap-datetimepicker-widget .btn[data-action="togglePeriod"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle AM/PM"}.bootstrap-datetimepicker-widget .btn[data-action="clear"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Clear the picker"}.bootstrap-datetimepicker-widget .btn[data-action="today"]::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Set the date to today"}.bootstrap-datetimepicker-widget .picker-switch{text-align:center}.bootstrap-datetimepicker-widget .picker-switch::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Toggle Date and Time Screens"}.bootstrap-datetimepicker-widget .picker-switch td{padding:0;margin:0;height:auto;width:auto;line-height:inherit}.bootstrap-datetimepicker-widget .picker-switch td span{line-height:2.5;height:2.5em;width:100%}.bootstrap-datetimepicker-widget table{width:100%;margin:0}.bootstrap-datetimepicker-widget table td,.bootstrap-datetimepicker-widget table th{text-align:center;border-radius:4px}.bootstrap-datetimepicker-widget table th{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table th.picker-switch{width:145px}.bootstrap-datetimepicker-widget table th.disabled,.bootstrap-datetimepicker-widget table th.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table th.prev::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Previous Month"}.bootstrap-datetimepicker-widget table th.next::after{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0;content:"Next Month"}.bootstrap-datetimepicker-widget table thead tr:first-child th{cursor:pointer}.bootstrap-datetimepicker-widget table thead tr:first-child th:hover{background:#eee}.bootstrap-datetimepicker-widget table td{height:54px;line-height:54px;width:54px}.bootstrap-datetimepicker-widget table td.cw{font-size:.8em;height:20px;line-height:20px;color:#777}.bootstrap-datetimepicker-widget table td.day{height:20px;line-height:20px;width:20px}.bootstrap-datetimepicker-widget table td.day:hover,.bootstrap-datetimepicker-widget table td.hour:hover,.bootstrap-datetimepicker-widget table td.minute:hover,.bootstrap-datetimepicker-widget table td.second:hover{background:#eee;cursor:pointer}.bootstrap-datetimepicker-widget table td.old,.bootstrap-datetimepicker-widget table td.new{color:#777}.bootstrap-datetimepicker-widget table td.today{position:relative}.bootstrap-datetimepicker-widget table td.today:before{content:'';display:inline-block;border:solid transparent;border-width:0 0 7px 7px;border-bottom-color:#337ab7;border-top-color:rgba(0,0,0,0.2);position:absolute;bottom:4px;right:4px}.bootstrap-datetimepicker-widget table td.active,.bootstrap-datetimepicker-widget table td.active:hover{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td.active.today:before{border-bottom-color:#fff}.bootstrap-datetimepicker-widget table td.disabled,.bootstrap-datetimepicker-widget table td.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget table td span{display:inline-block;width:54px;height:54px;line-height:54px;margin:2px 1.5px;cursor:pointer;border-radius:4px}.bootstrap-datetimepicker-widget table td span:hover{background:#eee}.bootstrap-datetimepicker-widget table td span.active{background-color:#337ab7;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.bootstrap-datetimepicker-widget table td span.old{color:#777}.bootstrap-datetimepicker-widget table td span.disabled,.bootstrap-datetimepicker-widget table td span.disabled:hover{background:none;color:#777;cursor:not-allowed}.bootstrap-datetimepicker-widget.usetwentyfour td.hour{height:27px;line-height:27px}.bootstrap-datetimepicker-widget.wider{width:21em}.bootstrap-datetimepicker-widget .datepicker-decades .decade{line-height:1.8em !important}.input-group.date .input-group-addon{cursor:pointer}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}
Oops, something went wrong.
@@ -7,10 +7,28 @@
* file that was distributed with this source code.
*/
-/**
- * Handling the modal confirmation message.
- */
(function ($) {
+ $(document).ready(function () {
+ hljs.initHighlightingOnLoad();
+
+ // Datetime picker initialization.
+ // See http://eonasdan.github.io/bootstrap-datetimepicker/
+ $('[data-toggle="datetimepicker"]').datetimepicker({
+ icons: {
+ time: 'fa fa-clock-o',
+ date: 'fa fa-calendar',
+ up: 'fa fa-chevron-up',
+ down: 'fa fa-chevron-down',
+ previous: 'fa fa-chevron-left',
+ next: 'fa fa-chevron-right',
+ today: 'fa fa-check-circle-o',
+ clear: 'fa fa-trash',
+ close: 'fa fa-remove'
+ }
+ });
+ });
+
+ // Handling the modal confirmation message.
$(document).on('submit', 'form[data-confirmation]', function (event) {
var $form = $(this),
$confirm = $('#confirmationModal');
Oops, something went wrong.
@@ -143,21 +143,16 @@
{# uncomment the following lines to combine and minimize JavaScript assets with Assetic
{% javascripts filter="?jsqueeze" output="js/app.js"
"%kernel.root_dir%/Resources/assets/js/jquery-2.1.4.js"
+ "%kernel.root_dir%/Resources/assets/js/moment.min.js"
"%kernel.root_dir%/Resources/assets/js/bootstrap-3.3.4.js"
"%kernel.root_dir%/Resources/assets/js/highlight.pack.js"
+ "%kernel.root_dir%/Resources/assets/js/bootstrap-datetimepicker.min.js"
"%kernel.root_dir%/Resources/assets/js/main.js" %}
<script src="{{ asset_url }}"></script>
{% endjavascripts %}
#}
<script src="{{ asset('js/app.js') }}"></script>
-
- <script>
- $(document).ready(function() {
- hljs.initHighlightingOnLoad();
- });
- </script>
{% endblock %}
-
</body>
</html>
@@ -0,0 +1,17 @@
+{#
+ Each field type is rendered by a template fragment, which is determined
+ by the value of your getName() method and the suffix _widget.
+
+ See http://symfony.com/doc/current/cookbook/form/create_custom_field_type.html#creating-a-template-for-the-field
+#}
+
+{% block app_datetimepicker_widget %}
+ {% spaceless %}
+ <div class="input-group date" data-toggle="datetimepicker">
+ {{ block('datetime_widget') }}
+ <span class="input-group-addon">
+ <span class="fa fa-calendar"></span>
+ </span>
+ </div>
+ {% endspaceless %}
+{% endblock %}
@@ -52,6 +52,7 @@ twig:
strict_variables: "%kernel.debug%"
form_themes:
- "bootstrap_3_layout.html.twig"
+ - "form/fields.html.twig"
# Assetic Configuration (used for managing web assets: CSS, JavaScript, Sass, etc.)
assetic:
@@ -28,6 +28,11 @@ services:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
+ app.form.type.datetimepicker:
+ class: AppBundle\Form\Type\DateTimePickerType
+ tags:
+ - { name: form.type, alias: app_datetimepicker }
+
# Uncomment the following lines to define a service for the Post Doctrine repository.
# It's not mandatory to create these services, but if you use repositories a lot,
# these services simplify your code:
@@ -49,8 +49,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
'label' => 'label.content',
))
->add('authorEmail', 'email', array('label' => 'label.author_email'))
- ->add('publishedAt', 'datetime', array(
- 'widget' => 'single_text',
+ ->add('publishedAt', 'app_datetimepicker', array(
'label' => 'label.published_at',
))
;
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace AppBundle\Form\Type;
+
+use AppBundle\Utils\MomentFormatConverter;
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\Form\FormView;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * Defines the custom form field type used to manipulate datetime values across
+ * Bootstrap Date\Time Picker javascript plugin.
+ * See http://symfony.com/doc/current/cookbook/create_custom_field_type.html
+ *
+ * @author Yonel Ceruto <yonelceruto@gmail.com>
+ */
+class DateTimePickerType extends AbstractType
+{
+ /**
+ * @var MomentFormatConverter
+ */
+ private $formatConverter;
+
+ public function __construct()
+ {
+ $this->formatConverter = new MomentFormatConverter();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildView(FormView $view, FormInterface $form, array $options)
+ {
+ $view->vars['attr']['data-date-format'] = $this->formatConverter->convert($options['format']);;
+ $view->vars['attr']['data-date-locale'] = \Locale::getDefault();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'widget' => 'single_text',
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParent()
+ {
+ return 'datetime';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return 'app_datetimepicker';
+ }
+}
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace AppBundle\Utils;
+
+/**
+ * This class is used to convert PHP date format to moment.js format
+ *
+ * @author Yonel Ceruto <yonelceruto@gmail.com>
+ */
+class MomentFormatConverter
+{
+ /**
+ * This defines the mapping between PHP ICU date format (key) and moment.js date format (value)
+ * For ICU formats see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax
+ * For Moment formats see http://momentjs.com/docs/#/displaying/format/
+ *
+ * @var array
+ */
+ private static $formatConvertRules = array(
+ // year
+ 'yyyy' => 'YYYY', 'yy' => 'YY', 'y' => 'YYYY',
+ // day
+ 'dd' => 'DD', 'd' => 'D',
+ // day of week
+ 'EE' => 'ddd', 'EEEEEE' => 'dd',
+ // timezone
+ 'ZZZZZ' => 'Z', 'ZZZ' => 'ZZ',
+ // letter 'T'
+ '\'T\'' => 'T',
+ );
+
+ /**
+ * Returns associated moment.js format.
+ *
+ * @param string $format PHP Date format
+ *
+ * @return string
+ */
+ public function convert($format)
+ {
+ return strtr($format, self::$formatConvertRules);
+ }
+}
View
Oops, something went wrong.
Oops, something went wrong.

0 comments on commit 4dd93aa

Please sign in to comment.