Form Widgets with jQuery UI

Hello!  In this tutorial we’re going to (finally!) finish the HTML5 contact form we’ve been developing.  Just a quick recap – in part one we went over the HTML markup and discussed the new types of input element;  in part two we prettied the form up a little using CSS3; in part three we wrote a JavaScript function to detect whether the user’s browser supports any of the new UI widgets; and in part four we used the jQuery Validate plugin to enable client side form validation.  In this tutorial, we’re going to use jQuery UI to enable form widgets if they aren’t supported natively.

Check out where we’re at thus far: Demo
You can view the finished product here

Before we start, I’m going to make a quick change to the form – I’m going to change the input type of the ‘project-budget’ field from ‘number’ to ‘range’, and remove validation for this input.  Now we have an extra UI widget to work with.

For this tutorial, we don’t need all of jQuery UI – we only need the core and a couple of the widgets.  So do we serve a custom jQuery UI file, which will be considerably smaller in size, or load the whole of jQuery UI from the Google CDN?  From a performance perspective, both options have pros and cons.  On the one hand, you have the smaller file size of a custom build, but on the other you have content being downloaded concurrently from a CDN, with the possibility that it’s already cached in the user’s browser. If the custom build was very small I would go for that option, but for the purposes of this tutorial, the simplicity of loading jQuery UI from Google’s CDN is just easier for me.  You should always consider each case on it’s own merit to get the best performance possible; but if you decide to use files on an external CDN, make sure that you always put a fallback in place in case the CDN is unavailable.

We need to include a reference to the jQuery UI base theme in the head of our page.

<link rel="stylesheet" href="screen.css">
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/themes/base/jquery-ui.css">

We already have some script files referenced in our html –

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

Lets change the version number on the jQuery core to 1.5, and import jQuery UI. Paste the following lines of JavaScript code into the bottom of the form HTML, just before the last body tag.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

Why do we put them at the bottom of the page?  We put them here so that they don’t block the loading and rendering of page content.  If you place a script in the head of a page, especially if it’s a large one, the browser retrieves and parses this script before flowing the content into the page.  So your user will be faced with a big fat blank page until the script has been fetched and parsed, which could be a considerable amount of time depending on how large your script is.  Not very user friendly!  Notice also that we’re linking to a specific version number, not the shorthand ‘latest’.  If you link to the ‘latest’ version, you don’t get the benefits of caching mentioned above, and run the (small) risk of backwards compatibility issues with your code when the next version of jQuery is released.

Below is the (refactored) support detection script that we wrote in part three.

(function() {
    var inputs = ['url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
        input = document.createElement('input')
        len = inputs.length,
        uiSupport = {},
        i = 0;

    for (; i < len; i++) {
        input.setAttribute('type', inputs[i]);

        if (input.type === 'text') {
            uiSupport[inputs[i]] = false;
        } else {
            input.value = 'testing';
            (input.value === 'testing') ? uiSupport[inputs[i]] = false : uiSupport[inputs[i]] = true;
        }
    }
})();

After this function has executed (i.e. immediately), the uiSupport object will contain the name of each input type, and a boolean value (true or false) describing whether it is supported in the browser.  Next we’re going to create an object to contain all of our widget methods.  We’re going to use the slider and the datepicker from jQuery UI.

...
        i = 0,
        widgets = {
            date : function(elem) {
                elem.datepicker({
                    beforeShow : function(input, inst) {
                        inst.dpDiv.css({
                            fontSize : '14px',
                            marginLeft : 215,
                            marginTop : -22
                        });
                    }
                });
            },
            range : function(elem) {
                elem
                    .after('<div></div><span class="slider-val">1500</span>')
                    .next()
                    .slider({
                        value : 1500,
                        min : 500,
                        max : 4000,
                        step : 500,
                        slide: function(event, ui) {
                            $(this).next().text(ui.value);
                        }
                    })
                    .end()
                    .remove();
            },
            altrange : function(elem) {
                elem
                    .addClass('ui-slider')
                    .after('<span class="slider-val">1500</span>')
                    .change(function() {
                        $(this).next().text($(this).val());
                    });
            }
        };

Then after the for loop, we’re going to use for…in to iterate over the uiSupport object to check support and call the relevant method:

    for (var prop in uiSupport) {
        if (prop === 'range') {
            widgets[(uiSupport[prop] ? 'alt' : '') + prop]($('input[type=' + prop + ']', 'form'));
        }
        if (prop === 'date' && !uiSupport[prop]) {
            widgets[prop]($('input[type=' + prop + ']', 'form'));
        }
    }

The last thing we need to do is to disable the ‘contact time’ field, unless the ‘phone’ radio button has been clicked.  Our complete function looks like this:

$.contactForm = (function() {

    var inputs = ['url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
        input = document.createElement('input')
        len = inputs.length,
        uiSupport = {},
        i = 0,
        widgets = {
            date : function(elem) {
                elem.datepicker({
                    beforeShow : function(input, inst) {
                        inst.dpDiv.css({
                            fontSize : '14px',
                            marginLeft : 215,
                            marginTop : -22
                        });
                    }
                });
            },
            range : function(elem) {
                elem
                    .after('<div></div><span class="slider-val">1500</span>')
                    .next()
                    .slider({
                        value : 1500,
                        min : 500,
                        max : 4000,
                        step : 500,
                        slide: function(event, ui) {
                            $(this).next().text(ui.value);
                        }
                    })
                    .end()
                    .remove();
            },
            altrange : function(elem) {
                elem
                    .addClass('ui-slider')
                    .after('<span class="slider-val">1500</span>')
                    .change(function() {
                        $(this).next().text($(this).val());
                    });
            }
        };

    for (; i < len; i++) {
        input.setAttribute('type', inputs[i]);

        if (input.type === 'text') {
            uiSupport[inputs[i]] = false;
        } else {
            input.value = 'testing';
            (input.value === 'testing') ? uiSupport[inputs[i]] = false : uiSupport[inputs[i]] = true;
        }
    }

    for (var prop in uiSupport) {
        if (prop === 'range') {
            widgets[(uiSupport[prop] ? 'alt' : '') + prop]($('input[type=' + prop + ']', 'form'));
        }
        if (prop === 'date' && !uiSupport[prop]) {
            widgets[prop]($('input[type=' + prop + ']', 'form'));
        }
    }

    $('input[type=time]').attr('disabled','disabled');
    $('input[type=radio]').click(function() {
        if ($(this).val() === 'Phone') {
            $('input[type=time]').removeAttr('disabled');
        } else {
            $('input[type=time]').attr('disabled','disabled');
        }
    });

})();

If we have a look at our form now, we can see that in Firefox, where none of the UI widgets are currently supported, we have the jQuery UI slider and datepicker:

In Chrome, where the range slider is supported but the datepicker isn’t, we see this:

And in Opera, where both the range slider and datepicker are supported natively, we see this:
You can view the final product at the link below.
Demo

I hope you’ve enjoyed this series of tutorials.  Please leave a comment below if you have any questions or suggestions.