Fork me on GitHub

10 Jul 2010

Rendering Grails Joda-Time date inputs cross browser with HTML5, jQuery and Modernizr

Yesterday I released a new version of the Grails Joda-Time plugin that includes support for the various new date and time input types in the HTML5 standard. Right now only Opera supports these types with proper date-picker type controls, the other browsers just render them as text inputs. However this doesn’t mean you can’t or shouldn’t start using them right away. There’s a very handy JavaScript library called Modernizr that you can use to detect the features supported by the client’s browser and render alternatives using script. In this post I’m going to walk through how to combine the Joda-Time plugin, Modernizr and the jQuery datepicker to render an HTML5 date input field that will bind to a LocalDate property on a domain object.

Install the Joda-Time plugin

If you don’t currently have version 1.1+ of the Joda-Time plugin installed, run grails install-plugin joda-time.

Once the plugin is installed you will need to enable HTML5 support so that data binding will use the HTML5 date/time formats. Simply add the following to your Config.groovy

jodatime.format.html5 = true

Create a domain class with a LocalDate property

For this example I’ll create a simple domain class representing a person:

import org.joda.time.*
import org.joda.time.contrib.hibernate.*

class Person {
    String name
    LocalDate birthdate
    static mapping = {
        birthdate type: PersistentLocalDate
    }
}

If you have default mappings set up (which the Joda-Time plugin will attempt to do for you when it installs) then you can omit the mapping block but I’ve included it for completeness.

Execute grails generate-all Person to create a controller and scaffolded views for the Person class. Finally change the create.gsp and edit.gsp files to use an HTML5 input instead of the standard Grails date picker tag. Replace the <g:datePicker name="birthdate"… with this:

<joda:dateField name="birthdate" value="${personInstance.birthdate}"/>

In the unlikely event that all your site’s visitors are using Opera then your work is done at this point. However right now anyone using another browser will have to type in the birthdate value in the correct format in order to get the forms to submit properly which isn’t the friendliest user experience.

Install jQuery and jQuery-UI

You can include jQuery and jQuery-UI by simply downloading the JavaScript files into your app, or linking to them on Google. Alternatively you can use the Grails plugins. If you want to go the plugin route this is what you will need to do. First install the plugins:

grails install-plugin jquery-ui

The jQuery plugin is installed automatically by the jQuery-UI plugin.

Then you will need to add the libraries to your SiteMesh template (or you could include it just on the relevant pages, it doesn’t matter). Add the following in the head section:

<g:javascript library="jquery" plugin="jquery"/>
<jqui:resources components="datepicker" mode="normal" />

Install Modernizr

Download Modernizr and put it in web-app/js/modernizr. You can link to the script direct from there or define a library by adding this to BootStrap.groovy:

import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib

class BootStrap {
    def init = { servletContext ->
         JavascriptTagLib.LIBRARY_MAPPINGS.modernizr = ["modernizr/modernizr-1.5.min"]
    }

Then include Modernizr in your SiteMesh template by adding the following in the head section:

<g:javascript library="modernizr"/>

To get Modernizr to work its magic you should add a class="no-js" to the html element at the top of your SiteMesh template. Modernizr will replace this when the document loads with a whole bunch of classes that it uses to detect the various features supported by the browser.

Bind the jQuery-UI datepicker to the field

The last step is to ensure that any time a user hits the page with a browser that doesn’t support a native widget for the date input type they get a jQuery datepicker instead. To do this create a JavaScript file and link to it from your SiteMesh template or the pages using date inputs. The script simply needs to contain the following code:

$(document).ready(function() {
    if (!Modernizr.inputtypes.date) {
        $("input[type=date]").datepicker({dateFormat: $.datepicker.W3C});
    }
});

What this does is create a function that runs on page load that uses Modernizr to determine if the date input type is supported and if not initialises a jQuery-UI datepicker for every <input type="date"… found in the page. The dateFormat argument ensures the jQuery widget will update the input with the correct date format when a value is selected.

It’s that simple. Now the create and edit person pages will use the native date input widget when the visitor’s browser supports one and the jQuery widget when it doesn’t.

So how does it look? Here's a screenshot of the page in Firefox when a visitor has focused the date input:

Here's the same thing rendered in Opera using the native datepicker widget (yes, you could be forgiven for thinking the jQuery version is prettier):

With a little adaptation the same thing can be done for the other input types; month, week, time, datetime and datetime-local. Of those month should work perfectly by adding this to your script:

if (!Modernizr.inputtypes.month) {
    $("input[type=month]").datepicker({dateFormat: 'yy-mm');
}

Other types would require different widgets to enable users to input the time portion of the value. There are several jQuery based options available. Of course, instead of jQuery you could use YUI, script.aculo.us or any other date/time widgets. I’ve gone with jQuery because I’m familiar with it and the initialisation is beautifully simple even if you’ve got a number of different inputs on a page.

8 comments:

Rob said...

I've just discovered that the input[type=date] selector doesn't work in Internet Explorer (of course). A workaround would be to put a class on any date inputs and select using that instead

Gardener said...

I meet an error:
Caused by: groovy.lang.MissingPropertyException: No such property: out for class
: com.energizedwork.grails.plugins.jodatime.Html5InputTagLib

Rob said...

In what context is that happening? Can you raise a JIRA with some code that reproduces the probelem?

Gardener said...

1.create a grails app
2.install joda-time
3.change config.groovy
4.create person domain class
5.grails generate-all Person,in this step,the error appeared.

Rob said...

I followed those steps using Grails 1.3.3 and it worked fine. Can you confirm the version of Grails you're using?

Gardener said...

Yes,Grails 1.3.3 is OK.
My enviroment is Grails 1.3.0.M1.
I wanna know whether Grails 1.3.2 is also ok?

Gardener said...

I have wrote an Chinese abstract according your blog ,and published here

sarbogast said...

I get the following exception with Grails 1.3.5:

Error processing GroovyPageView: Error executing tag : Error executing tag : Cannot write buffer to itself.