Sonntag, 17. Juli 2016

About ZF2, Bootstrap, jQuery UI, Modernizr and the case of missing date input field in Firefox

Recently I started to port an older project of mine, which I wrote years ago with Zend Framework 1 to the current stable and LTS branch of Zend Framework 2.4. In the course of this I'm learning the new code guidelines and to better separate the logic between the different layers of MVC.
Zend Framework 1 relied on Dojo Toolkit to have modern controls in form, this was mostly JavaScript workaround for missing features of HTML4. ZF2 on the other hand offers support for the newer HTML5 form controls. When I came to the date type input field, I learnt, that as of Firefox 47 this isn't supported yet.

input type date field as rendered by Firefox 47

And while the control looks good in Chrome, where I have native support and can use built-in calendar widget, in Firefox it's rendered like the plain input field of type text with no additional possibility to control, what the user types in, in which format and how to work around the different localised date formats used around the world.

input type date field as rendered by Chrome 51 with German locale
I already knew the Datepicker from jQuery UI project. So what I needed was some neat library, which could detect, if I use a browser, which does or doesn't properly support the date input field and optionally some Bootstrap theme for jQuery UI.
The browser ability detection seems to be implemented pretty well by Modernizr library. It has many different features, you can compile into the library, but only one special is of interest in this case: Form input types. Then you build it, you get a file called modernizr-custom.js, save it, we'll need it later.

There is also a Bootstrap theme for jQuery UI, which isn't officially up-to-date according to the version number, but the datepicker looks good with it.

As for the logical problem we not only need just the datepicker, as it will format the date beautiful, but this isn't usable for data processing, as in the backend and with proper HTML5 support the date sent by the form is in ISO 8601 format (YYYY-MM-DD). Luckily the datepicker supports filling of alternative fields with date in different format, so we need to create different and hidden input field, which will send the properly formatted date.

As I don't want to load those multiple JS and CSS files on every site, but only on sites with forms and I don't want to write the same code multiple times, the easiest way is a helper. In this example it is directly in the Application scope, so create the file /module/Application/src/Application/View/Helper/InputDateModernizrHelper.php and put the following content inside:

<?php

namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper;

class InputDateModernizrHelper extends AbstractHelper
{
    public function __invoke()
    {
        $this->view->headScript()->appendFile($this->view->basePath('/js/jquery-ui.js'));
        $this->view->headScript()->appendFile($this->view->basePath('/js/ui/i18n/datepicker-de.js');
        $this->view->headScript()->appendFile($this->view->basePath('/js/modernizr-custom.js'));
        $this->view->headLink()->appendStylesheet($this->view->basePath('/css/jquery-ui.css'));
        $this->view->headLink()->appendStylesheet($this->view->basePath('/css/jquery-ui-bootstrap-1.10.3.custom.css'));
        $this->view->headLink()->appendStylesheet($this->view->basePath('/css/jquery-ui-bootstrap-1.10.3.ie.css'), 'screen', 'lt IE 9');
        $this->view->headScript()->appendScript(
'$(document).ready(function() {
    if (!Modernizr.inputtypes.date) {
        $("input[type=date]").each(function(index) {
            var dateHelper = $(this).attr("name") + "_dateHelper_" + Math.random().toString(36).replace(/[^a-z]+/g, "");
            $(this).after("<input type=\'hidden\' id=\'" + dateHelper + "\' name=\'" + $(this).attr("name") + "\'>");
            $(this).attr("name", dateHelper);
            $(this).datepicker({
                altField: "#" + dateHelper,
                altFormat: "yy-mm-dd"
            });
        });
    }
});');
    }
}

To be able to easily use the helper, we have to register it in the file /module/Application/config/module.config.php by adding more or less one line of following code:

    'view_helpers' => array(
        'invokables'=> array(
            /* My other helpers... */
            'inputDateModernizrHelper' => 'Application\View\Helper\InputDateModernizrHelper',
        )
    ),

To use the helper in any view you just have to call following command:

$this->inputDateModernizrHelper();

And we are done! Of course other things aren't implemented like deleting the hidden value, if we change the content of the visible and formatted date field. The most easy way would be making the the visible field readonly and adding a small control to clear it and the invisible date field.

As of now, I'm content with this solution, but this is very hacked version. There is another interesting library, which uses so called polyfill techniques to detect unimplemented features and offer some JS replacement. It goes beyond simple detection and is more or less all-in-one package. The only caveat is that as far as I understand, it would only work properly for IE9 and newer and not below (at least the automatic detection could be buggy) and you need jQuery < v3. The magic library is called Webshim.
jQuery should be a big problem as v3 is fairly new and the old v1 branch is still supported with fixes, though it's more or less v2 branch with additional support for IE6, 7 and 8. If you don't have to support the legacy Windows XP (support for it ended on April 8th of 2014 unless you are crazy enough to hold on this system trying to get POSReady security updates or are actually paying for support), which runs only IE8 as the browser, you're good. Windows Vista is able to run IE9 and will receive support until April 11th of 2017, so you would run good with this combination.

P.S.
There is some compatibility layer called jQuery Migrate 3.0 plugin, which adds migration help and compatibility layer for deprecated APIs, so Webshim could also run with it perhaps.

Keine Kommentare:

Kommentar veröffentlichen