Gracefully Degrading AJAX with PHP and jQuery
Aug 19

The object of this tutorial is to introduce methods that can be used in just about any situation to provide gracefully degrading form post functionality that works with or without javascript. The form will be a lot nicer with javascript because it can do so without a complete page request and response. This is important to you and me because while we want fancy asynchronous data transfer on the web, we also don’t want to alienate people who browse without Javascript or use a dinosaur browser which can’t handle it. Asynchronous data requests and responses are much quicker because you can send and receive much less data than a full page response. This speeds almost everything up considerably.
The Form
The form is an integral part of this process as it provides the framework for the data that is to be sent to the script that processes it. The example I will be using today is a newsletter sign up form. The form code we’ll be using for the example is below.
<form action="newsletter_register.php" method="post"
id="newsletter_form">
<div>
<label for="name">Name:</label>
<input type="text" class="text" name="name" id="name"
title="Please enter your name here." />
<br />
<label for="email">Email:</label>
<input type="text" class="text" name="email" id="email"
title="Please enter your email address here." />
<br />
<input type="submit" value="Submit" id="submit"
name="submit" />
<input type="hidden" name="is_async" id="is_async"
value="false" />
</div>
</form>
Notice that in the form there is two regular text inputs, a hidden input and a submit button. Pay attention to the hidden input because it is integral to the graceful degradation used in this example.
In addition to the form we are going to need a container in which we can display any AJAX response messages. So I will add the following after the form:
<div id="async_notifications"></div>
The jQuery Goodness
Using jQuery we can override the default browser handling of the submit button. The following code should be placed at the end of the HTML file containing the form. Place it just before the closing body tag as shown in the sample below.
<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
// hide the async_notifications div at start.
$('#async_notifications').toggle();
$('#newsletter_form').submit(
function () {
$('#is_async').val(true);
var request = $(':input').serializeArray();
$('#newsletter_signup').html('<h4>Status</h4><p>' +
'Saving your information...</p>').fadeIn('fast');
$.post(
this.action,
request,
function(response) {
var caption = (response.result == true) ? 'Confirmed' : 'Error';
// show the response message
$('#async_notifications').html('<h4>' + caption + '</h4><p>' +
response.message + '</p>').slideDown('normal');
// hide the newsletter signup 'saving your information' div.
if (response.result == true) {
$('#newsletter_form').fadeOut('slow');
}
},
'json'
);
return false;
});
</script>
</body>
</html>
Notice in the JavaScript above, that I use the jQuery toggle() method to hide the notifications div when the page is completed loading. This allows me to use cool transition effects to add to the AJAXy feel of the response.
The Hook
The next line of code starts with $('#newsletter_form').submit(. This line is the critical component that overrides the submit behavior of the form. It is important to note also that the form code itself contains absolutely no JavaScript whatsoever. The intention here is to augment the form rather than force it into behavior we want. So that first line is saying, “target a form with the id of ‘newsletter_form’ and assign the following function as its’ submit handler.”
The next line — function () { — starts defining the handler function that will be assigned to the form’s submit event. Following the function definition is the first command that will be executed when the submit event of the form is triggered.
$('#is_async').val(true);
The line above basically says, “Target the html element that has an id of is_async and set its’ value to true. The line after that sets the variable request to the result of the jQuery serializeArray() method. We’re using a special selector here, ':input' which is a magic string which tells jQuery to target all forms of input elements. (<select>,<input>,<textarea>, etc. and collect them into a json object. So, our request data resides in the request variable. See the following code segment for the code described above.
var request = $(':input').serializeArray();
The next code line sets the contents of the notifications div to be a message informing the user that their request has started in an English Language sort of way. After switching the content of the div, it is faded in from nothing with the fadeIn() method.
$('#async_notifications').html('<h4>Status</h4><p>' +
'Saving your information...</p>').fadeIn('fast');
After notifying the user that the request has started, it is time to send the request. The next line of code begins the call to jQuery’s post() method.
$.post(
this.action,
request,
function (response) {
// [...]
The code above is the first two arguments for the post method. The location and the POST data we wish to transmit. We’re using JSON here, but you could use other formats. We also see the opening of the third argument. A function declaration with the argument response. This defines the response handler method that will process the response.
The POST Target or Action Page
On the subject of the POST data that is sent from the form data on the page. Switch your attention for a moment over to the script we’ll use to process the form’s request. This is the back-end of our form so it handles at least half of the responsibility of the gracefully degrading form. The POST Target or Action page will need to do something with the data presented to it. Then respond with some information. It must also format the response in one of two ways; As a JSON object as text or (X)HTML markup. for the sake of the tutorial we’re going to ignore the data. I’m going to assume that you know what to do with your data.
The PHP POST Handler
<?php
/* Start main processing of request */
$is_async = (!empty($_POST['is_async']) && $_POST['is_async'] == TRUE) ? TRUE : FALSE;
# assume you handle your data processing in save_data()...
$result = save_data($_POST);
# if save_data == TRUE then we send confirmation, error otherwise.
if($result) {
$data = array(
'message' => "Thanks, we saved your registration.");
} else {
$data = array(
'message' => "Sorry, we were unable to save your registration.");
}
send_response( $result, $data, $is_async );
/* end of file */
The code above takes the $_POST array and passes it to the save_data method. This is something you should write up to handle the data from the form. Then, assuming $result is a boolean, we call send_response to output the response in the appropriate format. Depending on what the value of $result is, we set the 'message' index of our $data array to an appropriate message string.
send_response()
/**
* Sends the appropriate response to the client
* @param boolean $result whether the request was successful
* @param array(mixed...) $data The response to send.
* @param boolean $is_async whether the request is asynchronous (AJAX) or not.
* @return void
*/
function send_response($result, $data, $is_async)
{
if ($is_async)
{
$func = 'send_json_stream';
} else {
$func = 'send_html_stream';
}
$func($result, $data);
}
The send_response method uses the $is_async variable to determine whether to respond with (X)HTML or JSON. So it sets the value of $func to the name of the function that needs to be called for either case. Then, we call the function named the value of $func and pass $result and $data to that function. Notice that send_response adds another index to our $data array. The 'result' index is added with the value of $result.
Note: If you are using some sort of MVC pattern or something you could use two different views to handle the different formats instead of placing it within functions.
send_json_stream()
/**
* Outputs $data as JSON encoded text.
* @param boolean $result Success or Failure
* @param array(mixed, ...) $data The response name:value pairs to send back.
*/
function send_json_stream($result, $data)
{
$data['result'] = $result;
echo json_encode($data);
exit;
}
The send_json_stream method does exactly what its name suggests. It outputs $data as JSON stream and stops execution of the script. See the example below for an idea of what the JSON object would look like.
{
"result" : true,
"message" : "Thanks, we saved your registration."
}
send_html_stream()
/**
* Outputs $data as XHTML code.
* @param boolean $result Success or Failure
* @param array(mixed, ...) $data The response name:value pairs to send back.
* @return NULL
*/
function send_html_stream( $result, $data)
{
$caption = ($result == TRUE) ? 'Confirmed' : 'Error';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Thank You</title>
</head>
<body>
<div id='async_notifications'>
<h4><?=$caption;?></h4>
<?=$data['message'];?><br />
</div>
<script type="text/javascript" src="js/jquery-1.3.2.min.js"></script>
<script type="text/javascript">
//<![CDATA[
// draw attention to the notification.
$('#async_notifications').slideDown('Normal');
//]]>
</script>
</body>
</html>
<?php
}
The send_html_stream() method responds with $data marked up in HTML matching the end-result of the intended AJAX request.
Displaying the Response
Now that we’ve established what the JSON response will be, we can start to understand how we’ll handle it. At this point I’m going to step backward. Remember the function we began defining in the jQuery post method call? It took one argument, response. Let us look at the rest of that function below.
function(response) {
var caption = (response.result == true) ? 'Confirmed' : 'Error';
// show the response message
$('#async_notifications').html('<h4>' + caption + '</h4><p>' +
response.message + '</p>').slideDown('normal');
// hide the newsletter signup 'saving your information' div.
if (response.result == true) {
$('#newsletter_form').fadeOut('slow');
}
}
You may recognize the logic in the first line of the function from our PHP send_html_stream method. They are logically identical. The next line sets the HTML of the div with the id of ‘async_notifications’ that we set up with the form. The HTML generated here is identical to that of send_html_stream. And for some flair, we add a .slideDown() call to the notification container div. Then for additional flare and completeness of context, we hide the HTML element with the id of ‘newsletter_form’ which happens to be our form element. This gives the user the feeling that they’ve completed the signup process. However, this only happens if the result of the request was true otherwise the user must revise and resubmit their form information.
The fourth and final argument passed to the jQuery post method is the string, 'json'. This indicates that we want to receive response as a JSON object.
Demo
You can find a demonstration of this technique at the following location: http://sholsinger.com/etc/sites/graceful_ajax_php/form.html
Conclusion
This technique is superb for little forms, for advanced interfaces, or complete operation of your site. One could even expand upon it to make something as complex as Google’s Gmail interface. The potential is limitless.
Who should use this technique?
- You already use jQuery on your site.
- You don’t want to add another module to your JavaScript library
- You value simplicity and web standards.
Who should not use this technique?
- You have read this entire tutorial.
- You still don’t know what the code in this tutorial does.
Downloads
You can download all the files used in this tutorial by following the link below or navigation to this location.
http://sholsinger.com/files/graceful_ajax_php.zip
You’re welcome to take any code you see in this tutorial and modify it to fit your needs. If you are doing so to expand the Tutorial, I’d appreciate a link back here along with a mention of my name. Wholesale copying of the tutorial itself is not authorized.