Fork me on GitHub
Showing posts with label html5. Show all posts
Showing posts with label html5. Show all posts

25 Mar 2011

Styling with HTML5 form validation

HTML5 specifies a number of enhancements to form inputs that are gradually being implemented by newer browsers. Among the enhancements is support for some level of automatic form validation. When inputs have invalid values the browser may refuse to submit the form. Currently Opera, Safari, Chrome and Firefox 4 have some support for this. IE8 does not. I haven’t experimented with IE9 yet.

Let’s look at an example. A simple registration form has inputs for the name, email and website of a prospective user. Of these name and email are required. The input elements are as follows:

<input type="text" name="name" required>
<input type="email" name="email" required>
<input type="url" name="website">

The required attribute is new in HTML5 as are the input types email and url. All of these have implications for validation. Additionally the new input types, whilst they appear just like regular text inputs in desktop browsers are great for mobile users as they are given optimised virtual keyboard layouts.

CSS3 has :invalid, :required and :optional pseudo-classes that are supported by current versions of Firefox, Chrome, Safari and Opera. Some browsers also refuse to submit the form if a required field is not filled in or invalid values are entered in url or email type fields. Obviously without appropriate feedback this could be a pretty disastrous user experience and thankfully even the Webkit browsers which until recently were blocking form submission without feedback are now handling things better.

  • Firefox 4, Opera 11.01 and Chrome 10.0.648.204 all block form submission if there is an empty required field or invalid value and display a message next to the first such field.
  • Safari 5.0.4 will apply :invalid CSS rules but allows the form to be submitted.

Applying styles to invalid fields is pretty easy. For example to highlight invalid fields with a red border and background:

input {
    border: 1px solid #ccc;
}

input:invalid {
    background: #fff3f3;
    border-color: #f66;
}

You can also combine the :invalid pseudo-class in combination with others. For example to highlight focused fields with a fancy CSS3 box-shadow:

input:focus {
    border-color: #99f;
    outline: none;
     -moz-box-shadow: 0 0 0.25em #99f; 
  -webkit-box-shadow: 0 0 0.25em #99f; 
          box-shadow: 0 0 0.25em #99f; 
}

input:focus:invalid {
     -moz-box-shadow: 0 0 0.25em #f66; 
  -webkit-box-shadow: 0 0 0.25em #f66; 
          box-shadow: 0 0 0.25em #f66;
}

It’s worth noting that Firefox 4 automatically adds a red box-shadow to invalid fields (and not just focused ones). To turn it off you can specify:

input:invalid {
    -moz-box-shadow: none;
}

Of course, the browsers that display messages when they refuse to submit a form do so with different text and different visual effects so creating a consistent cross-browser look and feel with both client and server side validation messages is going to be an interesting challenge.

6 Mar 2011

Date and time inputs vs. usability

Form input controls for date/time values have always been problematic. The more I experiment with different options, the more I think there is no one-size-fits-all solution.

Calendar widgets

Calendar widgets such as the jQuery UI date picker or the Opera HTML5 date input are very popular and can be a slick looking addition to a form. I don’t think they’re entirely positive though and certainly don’t work in all situations. They’re great when you don’t know exactly the date you want (I want to book a flight around Easter time) or you need to have an overview of the calendar (I need to set a reminder for GGUG on the third Tuesday of every month). However, when trying to enter your date of birth the widget is just in the way. Even if the widget is keyboard accessible and doesn’t force you to page month-by-month to the correct year you can almost certainly type the date quicker than you can find and select it in a calendar widget.

Multi-field inputs

A multi-field input works well for entering familiar dates such as your date of birth or for transcribing data. They’re not so good when you need the context a calendar widget affords (please don’t force your users to mentally calculate what date the 3rd Tuesday of next month is).

With a multi-field input it’s vital to remember that users in different places will expect to enter dates in a different order. For example, here in the UK we use the completely logical dd mm yyyy order whilst an American will, bizarrely, want to put the month before the day. It’s not particularly difficult to build a tag that renders the fields in a different order according to the request locale. Using the HTML5 placeholder attribute (or a suitable fallback for older browsers) is invaluable to ensure that users know which field is which.

A possible optimisation is to auto-tab between fields after the user has typed the requisite number of digits. In that case you should allow for the user to correct mistakes, so backspacing or using the cursor keys should also automatically jump between fields otherwise the auto-tabbing behaviour becomes more a hinderance than a help for fat-fingered typists like me.

Selects in a multi-field input

It’s very common to use a select input for some or all portions of the date. Personally, I pretty much detest this practice as it combines all the inconvenience of a calendar widget with none of the contextual information. Grails’ <g:datePicker> tag does exactly this. Not only are select elements like this bulking out the page with huge option lists (Grails’ datePicker dumps 8.6Kb of markup in your page without time fields!) but they are cumbersome to input values into with the keyboard because so many values start with the same character. An arguable exception is a select for the month field as presenting textual values rather than numbers is a little friendlier and the option list is short enough and the values distinct enough to allow for reasonably quick keyboard selection.

I suspect the rationale for using select elements is often that it all but ensures the user can’t enter an invalid value (I’ve even seen significant hoop-jumping done in Javascript to ensure values such as 30th February can’t be entered). I think it’s preferable to give the user a clear input and trust them to do the right thing with it rather than forcing them to use an awkward input so that they can’t get it wrong.

Simple textual inputs

You shouldn’t rule out using just a basic text input. In fact for monotonous data entry purposes it is probably optimal. The keystrokes to enter a date become muscle memory and there’s no fiddling about tabbing between day, month and year fields or grabbing the mouse to pick options from a select element. Obviously the down-side is that a plain text input is error-prone and very liable to confuse users unless the expected format is made clear.

HTML5

HTML5 specifies various new date and time input types which seems like good news but has some significant drawbacks at the moment. I can only imagine the new input types are designed to be rendered as native browser widgets (currently only Opera actually does so) as the required format is just about the most inconvenient imaginable for text entry. Sure, as a developer it’s appealing to require that dates have to be entered as yyyy-mm-dd as you no longer have to support varied input formats but no user in the world would naturally type a date that way. Therefore, to make such inputs usable you really need to use Javascript to proxy them with a widget or a set of multi-field inputs. Fine, but what about users who disable Javascript or use mobile or assistive devices that might not cope particularly well with the widgets? The fallback - a text input into which the user must type a machine-readable RFC3339 date - is pretty unfriendly.

What’s worse is that Webkit based browsers such as Chrome and Safari will render the new input types as regular text inputs but refuse to submit a form containing incorrectly formatted values and unbelievably provide no user feedback whatsoever when they do so.

I’m inclined to think that the useful support for the new input types is so minimal and the actively user-hostile handling of them so much more widespread that they’re best avoided altogether.

So, to sum up:

  • Think about who your users are and what they are trying to accomplish with this particular input field and give them the right input for the job at hand.
  • If they need to type some or all of the value make sure it’s clear what format is expected or which field is which.
  • Allow for people to enter values in the order they are comfortable with and above all don’t force them to type it in a machine-readable format.
  • Don’t strand Chrome and Safari users with broken HTML5 input types.

22 Feb 2011

JavaScript's history object, pushState and the back button

I’m not sure if it’s the immaturity of the browser support or my general uselessness but I’ve been having some trouble with the JavaScript history API.

I won’t try to explain the history API here, it’s pretty well covered at Mozilla Developer Network and W3. The basics are simple enough:

  • The API provides two methods; pushState which allows you to add a new entry to the browser history and replaceState which modifies the current history entry.
  • New entries added to history using pushState can be navigated via the browser’s back and forward buttons and a popstate event is fired on the window object when this happens.
  • Both methods allow you to attach arbitrary data to the history entry that you can use to reconstruct the appropriate page state when the user uses the back or forward buttons.

I imagine a pretty typical use-case is what I’ve been trying to do with the pagination and sorting on the list pages of Grails scaffolding. Instead of pagination links and column headers causing a full page reload when clicked I intercept the click event and send an AJAX request getting back a page fragment I can use to update the list in the page. Easy enough, however without the history API it will break the back and forward buttons and make deep linking impossible. This isn’t an acceptable option so in true progressive enhancement style I’ve used Modernizr and only apply the AJAX behaviour if the browser supports the history API.

The essence of the script involved is this:

var links =     //... pagination and sorting links
var container = //... the region of the page that will be updated with AJAX

// override clicks on the links
links.live('click', function() {
    // grab the link's URL
    var url = $(this).attr('href');
    // add a new history entry
    history.pushState({ path: url }, '', url);
    // load the page fragment into the container with AJAX
    container.load(url);
    // prevent the link click bubbling
    return false;
});

// handle the back and forward buttons
$(window).bind('popstate', function(event) {
    // if the event has our history data on it, load the page fragment with AJAX
    var state = event.originalEvent.state;
    if (state) {
        container.load(state.path);
    }
});

// when the page first loads update the history entry with the URL
// needed to recreate the 'first' page with AJAX
history.replaceState({ path: window.location.href }, '');

At first glance this works pretty nicely. In browsers that support history (right now that’s just Chrome, Safari and its mobile variants) paginating and sorting the list does not refresh the entire page but the browser’s location bar is updated so copy-pasting or bookmarking the URL will give a valid link to the current page. What’s more, the back and forward buttons can be used to step back through the list pages just as if we reloaded the whole page. In non-history-compliant browsers the list page behaves just like it always did; the links reload the entire page.

Unfortunately there’s a problem that was reported to me on GitHub shortly after I uploaded a demo of my scaffolding work. Where everything falls down is when you paginate the list, follow a link off the page (or just use a bookmark or type in a URL), then use the back button to return to it. In Chrome and iPad/iPhone variants of Safari the browser displays just the page fragment from the last AJAX call, losing all the surrounding page along with styling, scripts, etc.

Where things get very odd is that adding a Cache-Control: no-cache header to the AJAX responses makes the problem disappear, presumably because the browser then doesn’t cache the AJAX response and has to use the historical URL to reload the entire page. Remember, in good progressive enhancement style the URL for the full page or the fragment is the same. The server uses the X-Requested-With header to decide whether to return a fragment or the whole page. Obviously, forgoing caching is hardly an acceptable compromise but it’s interesting in that it reveals what the browser is doing. It can’t, surely, be right for the browser to treat a page fragment the same as a full document!

Curiously in desktop Safari this doesn’t happen and the full page is loaded as you would hope. Looking at the User-Agent header it looks like Safari is using an older version of WebKit (533.19.4 rather than 534.13).

You can see the behaviour in my scaffolding sample app. I also ran into the exact same issue today at work whilst trying to add history navigation to this page (which is a good example of why you’d want to use history in the first place). I don’t think it’s just me, either. The same problem can be seen with the demo linked from this post about the history API.

If there are any JavaScript experts out there who can point out my obvious incompetence here, that would be great. Otherwise I guess I’ll have to wait for updates to WebKit to hopefully fix the issue before I can start applying this technique with confidence.

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.