Ajax form with jQuery

Building an Ajax form with jQuery requires two components:

  1. a server side script to process data
  2. the ajax() object of jQuery

Suppose that you want a search form to get some definitions of web standards. The server-side script is as follows:

<?php

header('Content-Type: text/plain');

$q = $_GET['q'];
$response = '';

if(preg_match('/^\s+$/', $q) || empty($q)) {
    $response = '<p class="error">You must provide a search term.</p>';
    echo $response;
} else {

   $term = strtoupper($q);
   $definitions = array(
                  'CSS' => 'A stylesheet language.',
    'DOM' => 'An interface to manipulate web documents.',
    'XML' => 'An extensible markup language.'
    );
   
   foreach($definitions as $definition => $value) {
   
       if($definition == $term) {
       
           $response = '<div class="success"><h2>' . $definition . '</h2><p>' . $value . '</p></div>';
    
    break;
       
       } else {
       
       
           $response = '<p class="error">No term found.</p>';
    
    break;
       
       
       }
   
   
   }
   
   
   echo $response;
   
   
    
   


}
?>

Note that we're serving our response in plain text, because it's faster. Also note that we're simply using an associative array to mimic the behavior of a database. The jQuery code is really simple:

 $(document).ready(function() {
    
    
        $('#test').submit(function() {
 
 if($('p.error').size()) {
 
     $('p.error').remove();
     
 }
 
 var query = $('#q').val();
 var data = 'q=' + query;
 $.ajax({
 
     type: 'GET',
     url: 'process.php',
     data: data,
     success: function(html) {
     
        $('<div></div>').html(html).insertAfter('#test');
     
     
     }
 
 
 });
 
 return false;
 
        });
    
    
    });

First of all, you need to clean all the previous Ajax messages inserted after a form submission. Then you build up your query string and pass it to the ajax() object, also specifying the type of your request, the URL of the server-side script and the data passed with your query (in this case, it's a GET query). If the request is successful, you insert the result into an HTML element. Be aware that you have also to prevent the default action of the form from happening: if you don't do so, your browser will open the page specified in the action attribute of your form.

If you want to test this example, try to insert some valid values such as CSS, DOM and XML and then some dummy text. You can see the final result here.

CSS complex forms

Complex forms with CSS can be quite difficult to stylize because of the many existing discrepancies across browsers. Let's say that we want to stylize a form for ordering books. We could start with a markup like this:

<div id="order">
<h2>Order your books</h2>
<form action="#" method="post">


<table summary="Order: copies, code, title, price">

<tr>
    <th scope="col">Copies</th>
    <th scope="col">Code</th>
    <th scope="col">Title</th>
    <th scope="col">Price</th>
</tr>

<tr>
    <td><input type="text" name="copy1" id="copy1" /></td>
    <td><input type="text" name="code1" id="code1" /></td>
    <td><input type="text" name="title1" id="title1" /></td>
    <td><input type="text" name="price1" id="price1" /></td>
</tr>


<tr>
    <td><input type="text" name="copy2" id="copy2" /></td>
    <td><input type="text" name="code2" id="code2" /></td>
    <td><input type="text" name="title2" id="title2" /></td>
    <td><input type="text" name="price2" id="price2" /></td>
</tr>



<tr>
    <td><input type="text" name="copy3" id="copy3" /></td>
    <td><input type="text" name="code3" id="code3" /></td>
    <td><input type="text" name="title3" id="title3" /></td>
    <td><input type="text" name="price3" id="price3" /></td>
</tr>

<tr>
    <td><input type="text" name="copy4" id="copy4" /></td>
    <td><input type="text" name="code4" id="code4" /></td>
    <td><input type="text" name="title4" id="title4" /></td>
    <td><input type="text" name="price4" id="price4" /></td>
</tr>




</table>


<fieldset>
<legend>Payment method</legend>

<ul>
    <li>
        <label for="cod">COD</label>
        <input type="checkbox" name="cod" id="cod" value="cod" />
    </li>
    <li>
        <label for="visa">VISA</label>
        <input type="checkbox" name="visa" id="visa" value="visa" />
    </li>
    <li>
        <label for="american-express">American Express</label>
        <input type="checkbox" name="american-express" id="american-express" value="american-express" />
    </li>
    <li>
        <label for="master-card">Master Card</label>
        <input type="checkbox" name="master-card" id="master-card" value="master-card" />
    </li>
    
</ul>

<div class="footer">

    <div class="left">
    
        <label for="cc-number">N.</label>
        <input type="text" name="cc-number" id="cc-number" />
    
    </div>
    
    <div class="right">
    
         <label for="cc-expires">Expires</label>
         <input type="text" name="cc-expires" id="cc-expires" />
    
    
    </div>


</div>

</fieldset>

<ul class="customer-info">

    <li>
         <label for="name">Name</label>
        <input type="text" name="name" id="name" />
        <label for="address">Address</label>
        <input type="text" name="address" id="address" />
    </li>
    
    
    
    <li>
         <label for="city">City</label>
        <input type="text" name="city" id="city" />
        <label for="state">State</label>
        <input type="text" name="state" id="state" />
    </li>
    
    
    <li>
         <label for="phone">Phone</label>
        <input type="text" name="phone" id="phone" />
        <label for="email">Email</label>
        <input type="text" name="email" id="email" />
    </li>
    
    


</ul>


<p><input type="submit" name="submit" id="submit" value="Place order" /></p>




</form>
</div>

Why a table? It's quite simple to understand: in this form, data are organized in name/field pairs (name, title, price, etc.), so the most semantical way to mark up this is using a table. Of course not all data is contained within a table, so this is another issue to take into account. Here's our CSS styles:

/* General styles */

body, h2, form, fieldset, table, th, td, ul, p, legend {
 margin: 0;
 padding: 0;
 border: none;
 list-style: none;
 color: #333;
}


body {
 background: #fff;
 color: #333;
 font: 76% Arial, Helvetica, sans-serif;
}

h2 {
 font-size: 1.6em;
 font-weight: normal;
 color: #666;
}

/* Give input elements a consistent dimension */

input {
 font: 1em Arial, Helvetica, sans-serif;
}


/* Styling the main container */


#order {
 width: 700px;
 margin: 2em auto;
 padding: 8px 6px;
}

/* Styling the form element just for IE6/7 */

form {
 height: 100%;  /* http://www.satzansatz.de/cssd/onhavinglayout.html */
}

/* Styling the main title */

h2 {
 text-align: center;
 text-transform: uppercase;
}

/* Styling the table for order details */

form table {
 width: 100%;
 padding: 6px 0;
 border-spacing: 0;
 border-collapse: collapse;
 margin-bottom: 0.5em;
}

form table th {
 text-align: left;
 font-weight: normal;
}

form table td {
 padding-bottom: 3px;
}

form table td input {
 border: 1px solid #ccc;
 display: block;
 margin-right: 4px;
}

form table td #copy1,
form table td #copy2,
form table td #copy3,
form table td #copy4,
form table td #price1,
form table td #price2,
form table td #price3,
form table td #price4 {
 width: 50px;
}


form table td #code1,
form table td #code2,
form table td #code3,
form table td #code4 {
 width: 70px;
}

form table td #title1,
form table td #title2,
form table td #title3,
form table td #title4 {
 width: 505px;
}


/* Payment method: fieldset and its children */

fieldset legend {
 font-weight: bold;
 padding-bottom: 5px;
}

fieldset ul {
 width: 100%;
 height: 100%;
 overflow: hidden;
}

fieldset ul li {
 float: left;
 width: 172px;
 margin: 0;
}

fieldset ul li label {
 font-weight: bold;
 vertical-align: middle;
}

fieldset ul li input {
 vertical-align: middle;
}


fieldset div.footer {
 width: 100%;
 padding: 10px 0 20px 0;
 height: 100%;
 overflow: hidden;
}

fieldset div.footer div.left {
 float: left;
 width: 200px;
 margin: 0;
}

fieldset div.footer div.right {
 float: right;
 width: 200px;
 margin: 0;
 position: relative;
 left: 17px;
}

fieldset div.footer input {
 vertical-align: middle;
 border: none;
 border-bottom: 1px solid #ccc;
}
fieldset div.footer label {
 vertical-align: middle;
 font-weight: bold;
}


/* Customer info */


form ul.customer-info {
 width: 100%;
 padding: 15px 0;
 overflow: hidden;
 height: 100%;
}

form ul.customer-info li {
 float: left;
 width: 340px;
 margin: 0 4px 0 0;
}

form ul.customer-info input {
 vertical-align: middle;
 border: none;
 border-bottom: 1px solid #ccc;
 width: 110px;
 margin-right: 5px;
}

form ul.customer-info label {
 font-weight: bold;
 vertical-align: middle;
 padding-right: 4px;
}


/* Submit button */

form #submit {
 border-color: #ccc;
 background: #666;
 font-weight: bold;
 color: #fff;
}

The rendering of the fieldset element is inconsistent in Internet Explorer 7 and lower, so we have to fix it using conditional comments:

<!--[if lt IE 8]>
<style type="text/css" media="screen">
fieldset legend {
 position: relative;
 left: -6px;
}
</style>
<![endif]-->

View the live demo

The secret here for achieving a good consistency across browsers is to explicitly specify a font size and family on form elements. By doing so, we can actually have proportional form elements that scale gracefully along text.

Experimental forms with CSS

Experimental forms are quite a challenge with CSS. In this case, I've chosen to use an Italian postal order as a template. The markup is as follows:

<div id="bollettino">


<h2><span class="logo">Banco<span>Posta</span></span> CONTI CORRENTI POSTALI - Ricevuta di versamento</h2>

<form action="#" method="post">
<div id="ccn">
<label>sul C/C n.</label>
<table summary="Numero di conto corrente: riempire i campi con i caratteri necessari" id="numerocc1">
<tr>
<td><input type="text" name="char1" id="char1" size="2" /></td>
<td><input type="text" name="char2" id="char2" size="2" /></td>
<td><input type="text" name="char3" id="char3" size="2" /></td>
<td><input type="text" name="char4" id="char4" size="2" /></td>
<td><input type="text" name="char5" id="char5" size="2" /></td>
<td><input type="text" name="char6" id="char6" size="2" /></td>
<td><input type="text" name="char7" id="char7" size="2" /></td>
<td><input type="text" name="char8" id="char8" size="2" /></td>
</tr>
</table>
</div>

<div id="euro">
<label>di Euro</label>
<table summary="Importo da pagare: riempire i campi con le cifre necessarie" id="euro1">
<tr>
<td><input type="text" name="cifra1" id="cifra1" size="2" /></td>
<td><input type="text" name="cifra2" id="cifra2" size="2" /></td>
<td><input type="text" name="cifra3" id="cifra3" size="2" /></td>
<td><input type="text" name="cifra4" id="cifra4" size="2" /></td>
<td><input type="text" name="cifra5" id="cifra5" size="2" /></td>
<td><input type="text" name="cifra6" id="cifra6" size="2" /></td>
<td><input type="text" name="cifra7" id="cifra7" size="2" /></td>
<td><input type="text" name="cifra8" id="cifra8" size="2" /></td>
<td><input type="text" name="cifra9" id="cifra9" size="2" /></td>
<td><input type="text" name="cifra10" id="cifra10" size="2" /></td>
</tr>
</table>
</div>

<div id="importo">
<label for="importolettere">IMPORTO IN LETTERE</label>
<input type="text" name="importolettere" id="importolettere" />
</div>

<div id="intestato">
<label for="intestatoa">INTESTATO A</label>
<input type="text" name="intestatoa" id="intestatoa" />
</div>

<div id="causa">
<label for="causale">CAUSALE</label>
<input type="text" name="causale" id="causale" />
</div>

<p id="bollo">BOLLO DELL'UFFICIO POSTALE</p>

<div id="eseguito">
<label for="eseguitoda">ESEGUITO DA</label>
<input type="text" name="eseguitoda" id="eseguitoda" />
</div>

<div id="via">
<label for="via-piazza">VIA - PIAZZA</label>
<input type="text" name="via-piazza" id="via-piazza" />
</div>

<div id="caps">
<label for="cap">CAP</label>
<input type="text" name="cap" id="cap" />
<label for="localita">LOCALITÀ</label>
<input type="text" name="localita" id="localita" />
</div>




</form>


</div>

The CSS code is the following:

/* General styles */

body {
 margin: 2em 0;
 padding: 0;
 background: #fff;
 color: #000;
 font-family: Arial, Helvetica, sans-serif;
}

#bollettino {
 font-size: 76%;
 margin: 0 auto;
 width: 500px;
 padding-left: 1em;
 border-right: 1px solid #000;
}


/* Title */

h2 {
 margin: 0;
 font-size: 1em;
 font-weight: normal;
 border-bottom: 1px solid;
}

span.logo {
 float: right;
 margin: 0;
 padding-right: 1em;
}
span.logo span {
 font-weight: bold;
}


/* Form styles */

input {
 border: 1px solid #c00;
 vertical-align: 0;
}

label {
 font-weight: bold; 
 vertical-align: 5px;
}

table {
 display: inline;
}

td, td input {
 width: 18px;
 height: 22px;
}

#ccn, #euro, 
#importo, #intestato, 
#causa, #eseguito, 
#via, #caps {
 padding-top: 0.5em;
}


#importo label, #intestato label, 
#eseguito label, #via label, 
#caps label {
 vertical-align: -4px;
}




#euro1 {
 margin-left: 1.1em;
}

#importo input, #intestato input,
#eseguito input, #via input, 
#caps input {
 border-width: 0 0 1px 0; 
 border-style: dotted; 
 border-color: #000;
}

#causa {
 margin-top: 0.8em;
 margin-right: 1em;
 border: 1px solid #000;
 padding-bottom: 2em;
}

#causa input {
 border-width: 0 0 1px 0;
 border-style: dotted;
 border-color: #000;
 display: block;
 width: 100%;
}

#causa label {
 background: #fff;
 padding: 3px 3px 3px 0.3em;
 position: relative;
 top: -15px;
 left: 3px;
}

#bollo {
 margin: 0 1em 0 0;
 padding-top: 100px;
 text-align: right;
}

#cap {
 margin-right: 0.2em;
}

Internet Explorer needs the following additional code:

<!--[if IE]>
<style type="text/css">
#importo label, #intestato label, #eseguito label, #via label, #caps label {vertical-align: -3px;}
#causa>input {border-right: 1px solid; position: relative; width: 99.5%;}
</style>
<![endif]-->

Safari and Opera need a couple of JavaScript lines:

window.onload = function () {
  var inputs = document.getElementsByTagName("input");
  for (var i=0; i<inputs.length; i++) {
   if(inputs[i].getAttribute("id") == "causale") {
    if(navigator.userAgent.indexOf("Safari") != -1)  {
     inputs[i].style.width = "99.5%";
    } else if(navigator.userAgent.indexOf("Opera") != -1) {
     inputs[i].style.width = "483px";
    }
              
   }
  }
};

You can see the final result here:

HTML 5 form elements

An interesting article by Mark Pilgrim shows some of the most interesting features of HTML 5 form elements. For example, something that literally drove me mad is the following:

<input type="text" name="email" id="email" required="required" />

So you might write a JavaScript code like this:

$('input[required]').each(function() {
  // perform some validation tasks
});

Cool!

Styling form elements with CSS attribute selectors

The type of attribute selector we're going to use is called exact match attribute selector and has the form E[attribute="value"], where E is the element to match. We want to:

  1. give a border and a dimension to text fields
  2. give a dimension and a different background image to each label element.

Let's see how we can actually achieve this goal with attribute selectors.

form input[type="text"] {
  width: 150px;
  border: 1px solid #ccc;
}


form label[for="first-name"],
form label[for="last-name"],
form label[for="email"],
form label[for="subject"] {
 width: 140px;
 padding-right: 10px;
 display: block;
}

form label[for="first-name"],
form label[for="last-name"] {
 background: transparent url("../img/name.png") no-repeat 100% 100%;
}

form label[for="email"] {
 background: transparent url("../img/email.png") no-repeat 100% 100%;
}

We've specified a right padding on label elements which is equal to the width of the background images. By doing so, we avoid any possible overlapping of text and background images when the user resizes the text.

However, in Internet Explorer 7 the background images won't be displayed at all, because this browser doesn't allow this kind of styling on label elements. To avoid this problem, simply wrap the label elements with other elements (such as spans) and assign the styles of labels to them. Fortunately, this problem has been fixed in Internet Explorer 8.

CSS forms: handling input background images

Sometimes stylizing a form input field is something that we have to learn by experience. As always, a trial-and-error approach is the best way to acquire a firm knowledge of a technique. Suppose that you have a text field like this:

<input type="text" name="query" id="query" />

Now imagine that you want to use a background image on this field, something like a 200 x 20 GIF image with rounded corners and a surrounding shadow. An innocent coder would be tempted to write something like this:

input#query {
  border: none;
  width: 200px;
  height: 20px;
  background: url("text-field.gif") no-repeat;
}

It does work, but there's a major drawback with this approach: the text cursor appears on the very left edge on the field and not where it was supposed to appear. So you have a cursor that blinks almost outside the field and this is not the effect you want to achieve. Why this? Two considerations:

  1. the dimensions of this kind of form elements are not simply given by a width or height declaration, but also (and mostly) by the font size and family used on them; so if you want to get consistent results across browsers, you have to specify a font family and size that equals that used on the page (or at least on the direct ancestor of the element), because browsers assign by default a proprietary font family and size to this kind of elements
  2. input fields are not block-level elements, but inline-block elements, so you have to consider the fact that their rendering is treated differently.

A good solution to this problems is as follows:

input#query {
  border: none;
  width: 200px;
  padding: 5px;
  background: url("text-field.gif") no-repeat;
}

Instead of declaring height: value on the element, we use padding to make sure that the text cursor will be placed exactly where we want (obviously we have to adjust the amount of padding required to show the background image entirely). This is a cross-browser approach with no drawbacks.