Creating an iOS App with Cordova, Part 2: Prototyping the User Interface

It’s been a while since I wrote the first part of this article – some of the open source software I’d based the original post on has changed significantly.

The largest change is that PhoneGap is now called Cordova. After Nitobi’s acquisition by Adobe, the name of the project was changed, and stewardship handed to the Apache Foundation. Aside from confusing the hell out of people, this doesn’t affect us much, as for our purposes the API has stayed largely the same.

The framework I had been prototyping the UI with – Ratchet – has also changed. Previously it had only supported iOS-style visuals, but with version two, the Ratchet team have rewritten the framework, decoupling the theme component to support Android and other platform specific styles.

Prototyping the UI

Let’s start by opening Terminal, and creating a new folder to keep all our code in:

mkdir book-app
cd book-app

We’re going to use Bower to manage front end dependencies. If you haven’t heard of it before, Bower is a package manager for front end libraries – in the same way that npm is used for Node.js packages. I’m assuming that you already have Bower installed, so let’s create go ahead and a bower.json file:

bower init

Bower will ask you a load of questions to generate the bower.json file:

[?] name: book-app
[?] version: 0.0.1
[?] description: 
[?] main file: 
[?] what types of modules does this package expose? 
[?] keywords: 
[?] authors: Nikki <your@email.com>
[?] license: MIT
[?] homepage: 
[?] set currently installed components as dependencies? No
[?] add commonly ignored files to ignore list? Yes
[?] would you like to mark this package as private which prevents it from being accidentally published to the registry? YesN) y

{
  name: 'book-app',
  version: '0.0.1',
  authors: [
    'Nikki <your@email.com>'
  ],
  license: 'MIT',
  private: true,
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'test',
    'tests'
  ]
}

[?] Looks good? Yes

Once the config file has been created, install Ratchet and Jasmine, remembering to save them as dependencies.

bower install ratchet jasmine --save
Initial sketches

Next we’ll start creating the view. We’ve already got our sketches from the last tutorial, so with those in mind, lets flesh out the structure by writing the markup. Create a html file for each of the main three views…

touch index.html archive.html options.html

…and open up the Ratchet quick start guide at the page for the basic template.

Open up the index.html file in your text editor (I use love adore Sublime Text 3) and copy the header and footer of the basic template into it.  Change the CSS and JS file references to the correct ones.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Ratchet template page</title>

    <!-- Sets initial viewport load and disables zooming  -->
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

    <!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">

    <!-- Include the compiled Ratchet CSS -->
    <link rel="stylesheet" href="bower_components/ratchet/dist/css/ratchet.min.css">

    <!-- Include the compiled Ratchet JS -->
    <script src="bower_components/ratchet/dist/js/ratchet.min.js"></script>
  </head>
  <body>

...

  </body>
</html>

We also need to link to a theme, so paste the reference to the iOS theme just below the main styles:

<link rel="stylesheet" href="bower_components/ratchet/dist/css/ratchet-theme-ios.min.css">

Open up Ratchet’s components documentation.  In our UI sketches we designed an application bar at the top, and a navigation bar at the bottom, with a button for each of the main views.  Let’s create the top bar, with a button to the right for adding new books.

<header class="bar bar-nav">
  <a class="icon icon-plus pull-right" href="#"></a>
  <h1 class="title">ReadLog Stack</h1>
</header>

The Ratchet docs say that all bars must be added before the content, so let’s create that bottom bar next:

<nav class="bar bar-tab">
  <ul class="tab-inner">
    <li class="tab-item active">
      <a href="index.html">
        <span class="icon icon-bars"></span>
        <span class="tab-label">Stack</span>
      </a>
    </li>
    <li class="tab-item">
      <a href="archive.html">
        <span class="icon icon-pages"></span>
        <span class="tab-label">Archive</span>
      </a>
    </li>
    <li class="tab-item">
      <a href="options.html">
        <span class="icon icon-gear"></span>
        <span class="tab-label">Options</span>
      </a>
    </li>
  </ul>
</nav>

And finally, we’ll create the content container:

<div class="content">
  <ul>
    <li>Books</li>
    <li>Books</li>
    <li>Books</li>
  </ul>
</div>

Copy index.html and paste the markup into archive.html and options.html.  If you open this up in Chrome and enable mobile device emulation, you should see something that looks like this:

readlog-p2

I’m going to stop here for now. In part three, I’ll be writing failing tests in Jasmine, which we’ll use to discover more of our application’s functionality. We can then develop the UI accordingly.

You can find the code for this tutorial on Github: https://github.com/nikki/ReadLog-App-Tutorial

Be sure to check back in roughly a year’s time! By then – if you’re lucky – I might have finished part three 😉

Creating an iOS Application with PhoneGap, Part 1: User Experience

Hello, and welcome!  I’m going to show you how to create an iOS application – from start to finish – with HTML, CSS, JavaScript and PhoneGap.

In this first part, I’ll be planning the project. I’m going to start by discussing the importance of User Experience (UX) design, deciding upon features, and finishing off with some sketches. In part two, I’ll get down to prototyping the UI. In part three I’ll write failing tests with Jasmine. In part four, I’ll develop the application; and finally, in part five I’ll finish the app by wrapping it with PhoneGap, and deploying it to an iPhone with Xcode.

We’re going to create a really simple book cataloguing application, inspired by my personal experience with ReadMore (iOS).

ss1
ReadMore App by Navel Labs

ReadMore is an app I’ve used since 2010. It keeps track of the books you’re currently reading, books you’ve read, etc.; but it also logs how long it takes you to read each book. It’s a great little app, but the one big downside for me is that the time tracking functionality is tightly coupled with the archiving functionality. Time tracking is a feature I’m not interested in, but the app requires me to enter (fake) time data in order to save a book to the archive. I just want to be able to see, at a glance, the books I’m currently reading, and books I’ve read.

If I was being sensible, I’d simply browse the App Store for a suitable alternative; but that wouldn’t make a very interesting blog post.

Instead, I’m going to roll my own, and show you how I did it. Before I get started though, I’d like to take a moment to talk about user experience design.

User Experience Design

User experience is, at it’s core, concerned with one thing: how do users feel about your product? Is it intuitive? Cohesive? A delight to use? Or is using your product the pain point of a person’s day?

User experience designers, therefore, are engaged in the process of creating a positive user experience. This is accomplished by researching user needs, designing a solution to meet those needs, and validating the proffered solution with user testing.

Before user-centred design became the standard approach to designing software, designers approached projects with only a superficial understanding of it’s intended users. The design was based on what the designers thought the client wanted, as opposed to what they actually needed. The result was often disappointing.

tree_swing_development_requirements
Image credit: Paragon Innovations, Inc.

If your app is clunky and frustrating to use, you can be sure that users won’t stick with it for long. Creating a product that no one wants to use is a futile gesture, and a sure-fire way to bankrupt yourself in the process. This is why UX is so important! Customer loyalty? Employee productivity? User satisfaction? All are affected by UX, and all directly affect your bottom line.

Okay, you’re convinced. UXD is great. So where do you start? User Surveys? Personas? Wireframes? Usability Testing? Unfortunately, there is no right answer, no magical combination of UXD techniques that, applied to a project, will always guarantee a perfect outcome. But you can be assured that any effort you make to learn more about your users can only yield positive results. Less uncertainty means less time spent in fruitless debate, a smoother build process, and a better end product.

ux-diagram
Image credit: studio aum

Now that I’ve highlighted just how important UX is, I’m going to dive straight in at the deep end with some user stories. As a developer, I find that this is the best way to nail down a feature set, because each story represents a feature, and each feature is an actionable, measurable, and (most importantly) testable step to getting a product shipped.

Planning Features using User Stories

user story asteroids
Image credit: Andrew Fuqua

The template for a user story is as follows: As a user role I want feature so I can benefit. Using this template, we can describe all of the features we want the app to have. Let’s create the first user story for our book catalogue.

As a user, I want to be able to create a book record and save it to a list so that I can see which books I’m currently reading.

This one is pretty straightforward. In fact, this story represents the functionality of the app at it’s most basic level. It’s safe to say that similar stories will exist for the rest of the CRUD (create, read, update, delete) paradigm, so lets skip over them for now. (As we’re creating a single-role application, I’m going to forgo writing ‘as a user…’ for the remaining stories.)

… I want to be able to pre-populate a book record by searching for the book online, so that I don’t have enter a record manually.

Straightforward at first glance, but how should you be able to search specifically?

… I should be able to search by title, author, or barcode, to make it easy to enter a book record.

So, this feature confers a time-saving benefit. Imagine, though, that this were a commercial project. What if the project planner just assumed that the user should be able to search by title and nothing else? It’s not a great example, but you see the point I’m trying to make – loose ends can be a disaster for your project, so it’s best to keep asking questions until there are no more questions to ask.

I’ll probably use the Amazon or Google Books API here, and there’s an open source PhoneGap plugin I can use to read barcodes with a phone’s camera, so I have this part covered.

… I want to be able to view books by category, so I can see the different genres of books I’m reading.

This is a utility benefit. I read technical books for work, and sci-fi books for fun. It would be useful for me to be able to differentiate between the two at a glance.

… I want to be able to archive books that I’ve read, so that I only see books that I’m currently reading.

If we don’t separate ‘current’ books from ‘completed’ books, we’re going to end up with one long, not very useful list. Perhaps we could create a separate view for archived books?

… I want to be able to export data from the app, so that I can import it into other applications.

A user’s data belongs to the user, not to your app. They should be able to use it elsewhere (Goodreads, for example) if they choose to. I’ll export the book list to CSV and JSON, saving each as a document with the help of PhoneGap’s FileWriter API.

Also, backups are always useful. I lost a portion of my ReadMore reading list when I upgraded to iOS 7 beta, and I was incredibly annoyed about it. So, I’ll enable Document File Sharing in iTunes – any files exported will be accessible in here (it’s just a simple case of drag and drop to the desktop), and they’ll also automatically be saved with everything else when the user initiates a device backup.

These user stories describe the entire feature set of our application. In part two I’ll show you how to translate these stories into actionable steps with Behaviour Driven Design (BDD), but right now I’d like to talk about the UX challenges creating a hybrid app presents.

Hybrid App UX

User needs are one rung on the ladder of user experience. Your choice of tech stack can present it’s own problems.

Using a hybrid app can sometimes feel like you’ve taken a trip to the Uncanny Valley. The app may be work perfectly, but it doesn’t feel the way you expect it to. This is the unavoidable consequence of using a WebView; instead of having native controls and components to work with, you have to create everything from scratch with HTML, CSS and JavaScript. Users, accustomed to a platform’s behaviour, expect your app to follow the same conventions. Often this expectation goes unmet, and users can become frustrated with your app as a result.

For example, a button, if bound with a standard click handler rather than a touch handler, can take 300ms to trigger it’s associated event when tapped. 300ms of latency will not go unnoticed.

So, why bother with hybrid apps at all, why not just go native? Developing a hybrid app is still the fastest, most cost-effective way to get an app built and deployed across multiple platforms. The issues cited above only tend to arise when a developer takes a one-size-fits-all approach to building the app. Hybrid apps are not a panacea; if you plonk the same codebase down on iOS and Android without any modification, you’re going to have a bad time. Care must be taken to meet the expectations of users on each platform you intend to deploy to.

UX Sketching

On to sketching. I’m terrible at drawing, but sketching is always a useful exercise. The real value of sketching lies in how quickly you can spot obvious problems – a textual description of a feature can mask problems that will leap out at you when you put pencil to paper.

IMG_0078-1
Initial UX sketches

My all time favourite post on UX sketching is by Peiter Buick over on Smashing Magazine.  In his article, The Messy Art of UX Sketching, Peiter delves into the power of UX sketching, and discusses the tools and techniques behind it.  He explains it much better than I could, so it’s well worth a read.

Next Time…

In the next tutorial, I’ll be prototyping the user interface, and writing failing tests with Jasmine. Stay tuned!

Further Reading

Coding Horror – Avoiding the Uncanny Valley of User Interface
Smashing Magazine – Effectively Planning UX Projects

Quick Tip: CSS3 Transition End Event

If you’re working with CSS3 transforms and transitions, you detect the completion of an animation with the vendorTransitionEnd event.

So in Chrome and Safari:

document.addEventListener('webkitTransitionEnd', function(e) {
    console.log(e);
}, false);

will log the event object to the console. Opera uses oTransitionEnd, and Firefox just uses transitionend (note the lack of camel case for this one).

If you have multiple animations on multiple elements, you need to be able to tell which element has finished animating, and at what time.  You could either bind your event listener to each element respectively (which might get a bit messy), or you could rely on event delegation. Bind your event to the document as above, and e.target will reference the dom element that just finished animating.  e.target.className and e.target.id are very handy!

Portfolio Redesign

The redesign of my portfolio has finally gone live, I hope you like it! I wanted something a bit cleaner looking than the old site – I fiddled around for ages in Photoshop, but I’m a really harsh judge of my own work (I never feel like it’s good enough!) so I opted for a customised version of Classica instead.  Otherwise I’d never be finished!

There’s an easter egg on the homepage – it’s pretty good, so it’s almost a shame it’s hidden.  I think I might put it on it’s own page next week. (I should mention, it uses the HTML5 Canvas API, so it’s been disabled for anything that doesn’t support Canvas.  Get a better browser!)

In other news: I went to not one, but two of Remy (& Julieanne!) Sharp’s workshop’s recently – they were both part of the LeftLogic Big Workshop Tour.  The first was the HTML5 APIs one in Manchester, the second was the Node.JS last week in London.  They were both pretty awesome, and I met some really cool people, but I’m not going to write about them right now, because I couldn’t do them justice in this short blog post.  A workshop review is definitely on my to-do list.

Work on my jQuery liteAccordion plugin has slowed since it’s last release, but I have a lot of big plans for it when I finally get some free time (damn clients, paying my bills *grumble*)

Anyways, happy bank holiday!  If you have any feedback regarding the redesign (or the little surprise on the homepage) please let me know in the comments below.

Tschüss!

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.

jQuery Form Validation

In this tutorial, we’ll continue developing the HTML5 form that we’ve been working on.  We’re going to import jQuery and the jQuery Validate plugin into our page, so that we can validate any data entered into the form.  In the next part, we’ll work on propping up any HTML5 UI improvements that aren’t supported by the user’s browser using jQuery plugins.

At the bottom of our html contact form, just before the closing body tag, import jQuery from the Google CDN into the page.  Unfortunately, the Google CDN doesn’t currently host the jQuery validate plugin, but Microsoft do, so we’ll import it from their CDN.  After this, import a new script – I’ve called mine ‘global.js’ – into the page.  Don’t forget to create the new empty script file.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js"></script>
<script src="global.js"></script>

A lot of jQuery code samples you see start a script with the document.ready function.  There isn’t any need for us to do that here, as we’ve put our script tags at the bottom of the page – the document has already loaded before we’ve told the browser to fetch any scripts.

We’re going to check the data entered against the following patterns:

Fieldset 1

  • Name: letters, hyphens and spaces only, required field
  • Company: no validation
  • Email: email format (a@b.c), required field
  • Telephone: numbers only, min 11 chars, max 11 chars, required field
  • Website: url format

Fieldset 2

  • Type of Project: no validation (one item is selected by default), dynamically generate a textarea to collect details
  • Estimated Completion Date: no validation (we’ll use a jQuery datepicker plugin here later)
  • Estimated Budget: required field, numbers only
  • Additional Details: no validation

Fieldset 3

  • Please contact me by… : no validation required, as an option is selected by default
  • Best time to contact: disabled unless ‘contact by phone’ is selected

If all the tests pass, the form will be submitted; otherwise, the user will get instant feedback regarding any errors.  In global.js, write the following code:

$('form').validate();

If you specify which fields to validate in the html itself (usually by adding class=”required” to the input field you want to validate), this would initialise the plugin with the default options.  However, we want some custom validation and custom error messages, so we’re going to have to delve a little more deeply into the plugin’s interface.  For reference, all the methods associated with the plugin can be found here.

Options are specified by passing them into the validate() function with an object literal, as follows:

$('form').validate({
    rules : {}
});

This is where we will define the validation rules for each of our form inputs.  Each form input is referred to by it’s ‘name’ attribute.

$('form').validate({
    rules : {
        name : {},
        email : {},
        telephone : {},
        website : {},
        'project-budget' : {},
        'contact-time' : {}
    }
});

Notice that we’ve had to wrap any keys containing a hyphen in quotes, as hyphens are not permitted in property names.  Also, be sure to omit the comma for the last item of an object literal, as this will throw an error in IE.  Lets go through the form and add a ‘required’ rule for all the appropriate fields.

$('form').validate({
    rules : {
        name : {
            required : true
        },
        company : {},
        email :  {
            required : true
        },
        telephone :  {
            required : true
        },
        website : {},
        'project-budget' :  {
            required : true
        },
        'contact-time' : {}
    }
});

If we were to leave a field empty now, we’d see the default error message, telling us that the field is required.

Lets fill out the rest of the options.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    }
});

The above code is pretty self-explanatory, but for a couple of points.  Firstly, ‘required’ is not set to true or false on the ‘contact-time’ field, as we want the field to be a required field only if the ‘phone’ option is selected.  We need to pass the ‘required’ method a function containing an expression to be evaluated; this value is then set to be the return value of the function.  If the ‘phone’ option is selected, the expression will evaluate to true, so the field will be required, otherwise, the expression will be false, and the field will not be required.

Secondly, the ‘letters’ method is not a method native to the plugin, so we have to create a custom method using $.validator.addMethod.  We want to create a method that will check the data to match a pattern of letters, spaces and hyphens only.  We do this by matching the data against a regular expression.  Follow the code below as we build up a regular expression for matching lettters:

  1. [a-z] – lowercase letters only
  2. [a-zA-Z] – lowercase and capitals
  3. [- a-zA-Z] – lowercase, capitals, hyphen, space
  4. ^[- a-zA-Z]$ – ^marks the beginning of a pattern, $ marks the end
  5. ^[- a-zA-Z]+$ – the plus(+) sign after the brackets but before the end of the pattern denotes that we want to find more than 1 character
  6. /^[- a-zA-Z]+$/ – we wrap the enter regular expression in the JavaScript regex literal (//)

Lastly, we use the match method to check if the field data is a match:

6.   value.match(/^[- a-zA-Z]+$/);

The final code for the custom letters method is as follows:

$.validator.addMethod('letters', function(value) {
    return value.match(/^[- a-zA-Z]+$/);
});

Next we need to specify the error messages that we want to be displayed for each test, as below.

$('form').validate({
    rules : {
        name : {
            required : true,
            letters : true
        },
        email :  {
            required : true,
            email : true
        },
        telephone :  {
            required : true,
            digits : true,
            minlength : 11,
            maxlength : 11
        },
        website : {
            url : true
        },
        'project-budget' :  {
            required : true,
            digits : true
        },
        'contact-time' : {
            required : function(element) {
                return $('.contact-method input:last').is(':checked');
            }
        }
    },
    messages : {
        name : {
            required : 'The name field cannot be blank',
            letters : 'Please enter letters only'
        },
        email :  {
            required : 'The email field cannot be blank',
            email : 'Please enter a valid email address'
        },
        telephone :  {
            required : 'The telephone field cannot be blank',
            digits : 'Please enter numbers only',
            minlength : 'Please enter a UK phone number (11 digits)',
            maxlength : 'Please enter a UK phone number (11 digits)'
        },
        website : {
            url : 'Please enter a valid url'
        },
        'project-budget' :  {
            required : 'The project budget field cannot be blank',
            digits : 'Please enter numbers only'
        },
        'contact-time' : {
            required : 'The contact time field cannot be blank'
        }
    }
});

That’s all the validation we’re going to do in this part of the tutorial, in the next section we’ll improve the UI with widgets in browsers that don’t support the native HTML5 form improvements yet. Before we finish, we’ll just tidy up those error messages.

From the screenshot above, we can see that the errors are displaying in the wrong place – they should be underneath the error field, not to the bottom left where the field labels are.  To remedy this, we simply need to override the default css for a label element.  Fortunately, errors generated by the plugin have the class “error”, so we can simply target this class:

label.error { text-align: left; margin-left: 200px; color: #CC0000; padding: 0; height: auto; width: 208px; font-size: 11px }
input.error { border: 2px solid #CC0000 }

View the Demo

Images and CSS3 Gradients

Webkit has a range of css3 properties for working with alpha transparent masks (see webkit.org). Firefox doesn’t support these yet, but if you want to implement a basic linear or radial (non-alpha) gradient mask, you can fake it by blending a gradient with the background colour of your page.

Set your image as the background image of a div, and give the div a width and a height.

div {
    height: 384px;
    width: 283px;
    background: url('lich-king.jpg') no-repeat;
}

Duplicate the background property:

div {
    height: 384px;
    width: 283px;
    background: url('lich-king.jpg') no-repeat;
    background: url('lich-king.jpg') no-repeat;
}

Now paste a linear gradient property before the image value, with a comma after it (the order is important):

div {
    height: 384px;
    width: 283px;
    background: -webkit-gradient(linear,left bottom,left top,color-stop(0, rgba(27,223,72,1)),color-stop(1, rgba(27,223,72,0))), url('lich-king.jpg') no-repeat;
    background: -moz-linear-gradient(center bottom,rgba(27,223,72,1) 0%,rgba(27,223,72,0) 100%), url('lich-king.jpg') no-repeat;
}

See the green colour fading in? That’s the linear gradient we’ve created (inspect the element!). Pretty ugly, huh? Lets look at some practical examples:

Linear gradient blend (right to left)

div {
    height: 384px;
    width: 283px;
    background: -webkit-gradient(linear,right top,left top,color-stop(0, rgba(255,255,255,1)),color-stop(1, rgba(255,255,255,0))), url('lich-king.jpg') no-repeat;
    background: -moz-linear-gradient(right center,rgba(255,255,255,1) 0%,rgba(255,255,255,0) 100%), url('lich-king.jpg') no-repeat
}

Radial gradient blend

div {
    height: 384px;
    width: 283px;
    background: -webkit-gradient(radial, 50% 50%, 120, 50% 50%, 140, from(rgba(255,255,255,0)), to(rgba(255,255,255,1))), url('http://nicolahibbert.com/wp-content/uploads/2010/09/lich-king.jpg') no-repeat
    background: -moz-radial-gradient(50% 50% 0deg, circle closest-side, rgba(255,255,255,0), rgba(255,255,255,0) 85%, rgba(255,255,255,1)), url('http://nicolahibbert.com/wp-content/uploads/2010/09/lich-king.jpg') no-repeat;
}

HTML5 Forms Support Detection with JavaScript

In this tutorial, we’ll continue developing our HTML5 form.  In the last tutorial, we styled the form with some of the new CSS3 properties; this time we’re going to detect support for the new HTML5 input types.

We need to detect whether the browser supports the new HTML5 input types natively – if not, we can use this knowledge to fall back to JavaScript where appropriate.  It is important to note that there currently isn’t a lot of browser support for these input types: Opera 10.6 has the most complete support, Safari 5 and Chrome 5 have partial support, Firefox 3.6.8 and IE8 (as usual) are dead in the water.  If you’re dabbling with HTML5 at all, you’ll know that support is patchy at best, and you should treat tutorials like this one as instructional rather than definitive.  So whilst this information is current now, the specification is still undergoing revision (and will be for a while yet!).

Support Detection

If a browser doesn’t support a particular attribute value, it will degrade to the default value text.  In theory, this is how we test for browser support – we create a new input element in JavaScript (in memory only, don’t append it to the DOM), assign to it the attribute value we want to detect support for, and then query the element’s type attribute.  If the attribute retains the value that you assigned to it, the browser supports it.  Otherwise, if it returns the default value, the browser doesn’t support it. Using this method, we can loop through all the new input types and log the results to console to see what we get back:

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length;

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    console.log(inputs[i] + ' -> ' + input.type);
}

Notice that I began the above statement with the qualifier “in theory.”  Whilst this method should technically work, some browsers are prone to giving false positives, i.e. asserting that they support a particular input type, when in reality, they don’t.  Take a look at the output produced for the basic test above:

Only Firefox, IE8 and Opera are telling the truth! If you visually check in Safari and Chrome whether the UI improvements or validation functionality is present (using Mike Taylor’s HTML5 Input form, for example), it’s pretty evident that Safari and Chrome are not being entirely honest with us.  So how do we test for support if two of the major browsers are telling porkies?  Well, you have to go a step further and test for both UI improvements and validation functionality manually.

However, support for data validation is currently pretty flakey – that’s interpreting it generously.  It appears that some browsers validate the data properly, whilst others don’t check the data but pass it as valid anyway (see this discussion for details).  This probably won’t be resolved for a long time, so we’re going to validate data with JavaScript – experiments are one thing, but the state of implementation is so fragmented at this point that it’s not actually of any use to us.  So in the next part of this tutorial, validation will be handled by JavaScript exclusively.  What about the UI improvements?  If the UI improvements are supported, we’ll use those, otherwise we’ll provide the functionality ourselves.  Sounds like a plan!

We need to test for these UI improvements to see if they’re present in the browser.  UI improvements can be best described as things that we’d usually have to implement a widget for in Calendar in OperaJavaScript –  like a date picker or calendar – except now they’re implemented by the browser. We test for them by passing a text value to our mock-input, and then checking what gets returned – any new UI functionality shouldn’t be able to hold a text value.  If a string is returned, there’s no UI improvement; if anything else is returned (e.g. a numerical value in the case of range, or an empty value in most other cases) then a UI improvement is present.  One caveat: there are no detectable improvements or validation where the telephone and search inputs are concerned as yet, so we have to eliminate these from the first part of the test.

var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
len = inputs.length,
uiSupport = [];

for (var i = 0; i < len; i++) {
    var input = document.createElement('input');
    input.setAttribute('type', inputs[i]);
    var notText = input.type !== 'text';

    if (notText && input.type !== 'search' && input.type !== 'tel') {
        input.value = 'testing';
        if (input.value !== 'testing') {
            console.log(input.type);
        }
    }
}

In the FF and IE consoles, we don’t get any data returned, as expected.  Safari and Chrome return a non-string value for range only.  Opera returns a non-string value for datetime, date, month, week, time, datetime-local, number and range.  The last thing we have to do is store this information in an array so that we can refer to it later, then wrap the whole code block in a function so that the variables have some semblance of a namespace.

(function() {
    var inputs = ['search', 'tel', 'url', 'email', 'datetime', 'date', 'month', 'week', 'time', 'datetime-local', 'number', 'color', 'range'],
    len = inputs.length,
    uiSupport = [];

    for (var i = 0; i < len; i++) {
        var input = document.createElement('input');
        input.setAttribute('type', inputs[i]);
        var notText = input.type !== 'text';

        if (notText && input.type !== 'search' && input.type !== 'tel') {
            input.value = 'testing';
            if (input.value !== 'testing') {
                uiSupport.push(input.type);
                // console.log(uiSupport);
            }
        }
    }
})();

uiSupport will store the names of the input type attributes that are supported, so we can use them when we come to create our fallbacks in the next tutorial.

Quick Tip: If you need to detect support for some of the other input attributes – autofocus, for example, or any of the other new HTML5 elements, it’s definately worth checking out Modernizr.  Modernizr has the added benefit of having IE support baked in, so there’s no need to include an additional HTML5 shim to get the new (header, section, etc.) tags working.

Form Styling with CSS3

Today we’re going to continue developing our HTML5 contact form, using CSS.

Extract the inline CSS from the head of the form and paste it into a new document.  Name this new stylesheet style.css and make a reference to it in the head of the form HTML.  At the top of style.css, I’ve imported Eric Meyer’s stylesheet reset (named “reset.css”) and removed the star (*) selector that temporarily reset the margin and padding.  (NB: never use the start selector in production!)  Your stylesheet should now look like this:

@charset "utf-8";
@import "reset.css";

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { border: 0; padding: 10px; margin-bottom: 10px; background: #F6F6F6 }
legend { padding: 5px 10px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 33px }
select { width: 206px }
p { overflow: hidden; margin-bottom: 10px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

We’re just going to make a quick couple of changes before we start.

Firstly, add ‘height: 26px; line-height: 26px’ to the ‘label, span’ selector to vertically centre the text within the input labels.

Secondly, there’s a bit of a bug in IE concerning legends and fieldsets – IE bleeds the background colour of the fieldset behind the legend.  To recify this, we’re going to add a little bit more margin to the bottom of the fieldset, and position the legend absolutely within it.

Unfortunately, positioning the legends absolutely within a fieldset that has padding triggers a bug in Firefox.  This bug causes Firefox to calculate the legend’s position incorrectly, so to fix this, we’re going to remove the padding from the top of the fieldset, and add that padding to the first paragraph in the fieldset instead.  Add “padding-top: 0” to the fieldset attribute, and “p:first-of-type { padding-top: 25px }” underneath the fieldset style.  I rarely see :first-of-type used out in the wild, as :first-child can often be used in a similar context.  However, :first-of-type selects the element that is the first sibling of that type within it’s parent, rather than just selecting the first child.  Very useful!

Now that we’ve fixed that, we’ve broken IE again by removing the padding. Add the hack “padding-top: 25px9” to the fieldset selector and we should be good to go.

With these changes, your css should look like this:

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6 }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 33px }
select { width: 206px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Now that we have a base template for our form, we can start adding styles.

Border Radius

We’re going to add a rounded corner to the fieldsets, legends and submit button, add background and text colours to the legends, and change the style of the submit button. At the moment, we still have to use vendor prefixes for border-radius, so this style declaration usually consists of three lines:

#style {
    -webkit-border-radius: <value>;
    -moz-border-radius: <value>;
    border-radius: <value>;
}

Edit your css using the styles highlighted below.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px }
legend { padding: 5px 10px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px }
.submit { width: 100px; float: right; margin-right: 38px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

The last fieldset on the form should look like this:

I think it’s easy to go overboard with border-radius, so we’ll leave that bit there for now.  Next up, background gradients.

Background Gradients

We have to use vendor prefixes to enable this to work in Firefox and Webkit.  Unlike border radius, each vendor uses a different syntax for their style declaration, so it can be a little confusing.  There are more properties associated with setting a background gradient style, but I’ve listed the most common ones below.

Firefox has linear gradient and radial gradient values…

#style {
    background-image: -moz-linear-gradient( [<point> || <angle>,]? <stop>, <stop> [, <stop>]* );
    background-image: -moz-radial-gradient( [<position> || <angle>,]? [<shape> || <size>,]? <stop>, <stop>[, <stop>]* )
}

…whereas Webkit allows you to specify whether the gradient is a linear or radial one within “-webkit-gradient”:

#style {
    background-image: -webkit-gradient(<type>, <point> [, <radius>]?, <point> [, <radius>]? [, <stop>]*)
}

Note that you can also specify these gradients within the “background” shorthand property.

For our form, we’ll be using a grey border on the input boxes, and a slight gradient on the fieldsets.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EEEEEE), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%) }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -36px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

After these styles have been added, you should see something that looks like the image below.

Next we’re going to add a drop shadow to make the bottom corners of the fieldsets a little more prominent.

Box Shadow

Box shadow is similar to border-radius syntax-wise, you need to specify two vendor prefix properties and the official property as defined in the CSS3 spec.  Border-radius takes 3 lengths: the horizontal offset, the vertical offset, and the blur radius; and a colour.

#style {
    -webkit-box-shadow: <horz>, <vert>, <blur>, <color>;
    -moz-box-shadow: <horz>, <vert>, <blur>, <color>;
    box-shadow: <horz>, <vert>, <blur>, <color>;
}

We’re going to add a box shadow to the fieldsets, the legends, and the submit button, using the values highlighted below.  Notice that we’re also adding 4px margin to prevent the overflow: auto property on the parent paragraph tag from clipping the shadow on the bottom of the submit button.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Very Useful Tip: You can also use rgba values with box shadow, achieving an effect similar to the one I described in my CSS3 Alpha Transparent Gradients post.

Text Shadow

Next we’ll add a bit of text shadow on the legends and submit button to make them stand out a little more.  The text shadow property is pretty much the same as box shadow.  It takes 3 values and a colour, that is, the x value of the shadow, the y value of the shadow, the blur radius, and a colour.  However, this time we don’t have to use vendor prefixes, as native property is supported in all current browsers.

#style {
    text-shadow: <horz>, <vert>, <blur>, <color>;
}

Add the highlighted lines below to your css.

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; padding-top: 25px9; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; text-shadow: 1px 1px 1px #333 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px; text-shadow: 1px 1px 1px #333 }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
input:focus, textarea:focus, select:focus { background: white; border-color: #666 }
select { width: 202px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

Focus Highlighting

We’re going to add one last style, to make the input boxes more prominent when they’re in focus…

nput:focus, textarea:focus, select:focus { border-color: #666 }

…and one last tweak, to make the input boxes a little bigger.

input, textarea, select { padding: 3px; float: left; width: 200px; border: 1px solid #d9d9d9 }
select { width: 208px }

The final CSS should look like this:

form { width: 460px; padding: 20px; color: #333333; font-family: Verdana, Geneva, sans-serif; font-size: 12px; margin: 0 auto }
fieldset { position: relative; padding: 10px; padding-top: 0; margin-bottom: 30px; background: #F6F6F6; -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); box-shadow: 3px 3px 10px #ccc; -moz-box-shadow: 3px 3px 10px #ccc; -webkit-box-shadow: 3px 3px 10px #ccc }
legend { padding: 6px 12px; position: absolute; left: 10px; top: -11px; background-color: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; text-shadow: 1px 1px 1px #333 }
label, span { float: left; clear: left; display: block; width: 180px; padding-right: 20px; text-align: right; height: 26px; line-height: 26px }
input, textarea, select { padding: 3px; float: left; width: 200px; border: 1px solid #d9d9d9 }
.submit { width: 100px; float: right; margin-right: 37px; border: 0; padding: 5px 10px; background: #4F709F; color: white; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; box-shadow: 2px 2px 4px #888; -moz-box-shadow: 2px 2px 4px #888; -webkit-box-shadow: 2px 2px 4px #888; margin-bottom: 4px; text-shadow: 1px 1px 1px #333 }
textarea { background: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), color-stop(1%, #EEEEEE), to(#FFFFFF)) }
input:focus, textarea:focus, select:focus { background: white; border-color: #666 }
select { width: 208px }
p { overflow: hidden; margin-bottom: 10px }
p:first-of-type { padding-top: 25px }
.contact-method label { clear: none; width: auto }
.contact-method input { float: none; width: 20px }

View Demo

In the next tutorial, I’ll show you how to test for browser support of the new input types.