The right way to add AJAX support to your site Written on May 22, 2011, by danbrown.
It seems that every website now has at least some AJAX component to it. Whether it is an updating Twitter feed, checking if a username is available or a full-blown web app, they all have one thing in common – A connection back to a server via an external API. It might return be a single letter or a 500k XML file but you have a defined way to request and retrieve your data.
But what if that external API gives different results than when you load the page? This is usually a case of duplicated code where only one copy gets changed. We want to make sure that someone loading the page and someone getting an AJAX update always get the exact same data at a given point in time.
There is the dumb solution that I have written about before – not outputting it at all and doing an immediate AJAX update when the page first loads. This post is about a much more correct solution.
The most sensible way is to generate that data via a single function and use it in both places. You want to be defining an internal API where your main site display code and AJAX code are both consumers of it. By doing this you also make sure that your business logic is neatly partitioned away and any changes to it are then reflected throughout. People who use MVC frameworks already do this as a matter of course and their views (display logic) are kept well apart from the models (business logic).
Example
Lets look at a simple example that we want to AJAXify. We have a page that shows the status of a few servers and want it to update every five minutes. I am only giving snippets of code here, not a full working page and no error checking. The code used is PHP with jQuery but it doesn’t matter, it applies to ASP.net, Ruby, Perl, ColdFusion, etc just as much. I’ll use REST for the AJAX calls for simplicity.
First up, the original non-AJAX code -
index.php
...
<ul class="server_status">
?>
// Get statuses
$query = "SELECT server_name, server_status FROM servers;"
$result = mysql_query($query);
while ($server = mysql_fetch_array($result))
{
echo '<li>'.$server['server_name']. - '.$server['server_status'].'</li>';
}
<?php
</ul>
...
Nice and simple and puts the results in an unordered list, to be styled as the designer sees fit.
The wrong way to add AJAX
This is the “solution” I mentioned in my previous post. To summarise the three major flaws -
- An ugly user experience when the page loads in stages.
- No graceful degradation – The site is left with a gaping hole or broken when JavaScript isn’t available.
- The AJAX data is not available to search engine crawlers and so they won’t index that content.
See twitter.com while logged in with a slow connection for a high-profile example of the first one, and with javascript disabled for the second…
index.php
...
<ul class="server_status" id="server_status">
<li>Checking server statuses...</li>
</ul>
...
<script type='text/javascript'>
function UpdateServerStatus(result)
{
$("#server_status").html = result;
// Get new statuses after 5 minutes
setTimeout(GetServerStatus, 5*60*1000);
}
function GetServerStatus()
{
// AJAX call to get new statuses
$.ajax({url:"get_status_ajax.php", success: UpdateServerStatus, cache:false});
}
$(document).ready(GetServerStatus);
</script>
get_status_ajax.php
<?php
// Get statuses
$query = "SELECT server_name, server_status FROM servers;"
$result = mysql_query($query);
while ($server = mysql_fetch_array($result))
{
echo '<li>'.$server['server_name']. - '.$server['server_status'].'</li>';
}
?>
This code loads an empty status list then does an AJAX call to fill it in and then again every 5 minutes.
The better way to add AJAX
A better solution is to wrap any features that will be exposed to both external and internal requests with a function or small API. Then just call it where needed.
index.php
<?php
include_once('status_funcs.php');
?>
...
<ul class="server_status" id="server_status">
<?php echo GetStatus(); ?>
</ul>
...
<script type='text/javascript'>
function UpdateServerStatus(result)
{
$("#server_status").html = result;
// Get new statuses after 5 minutes
setTimeout(GetServerStatus, 5*60*1000);
}
function GetServerStatus()
{
// AJAX call to get new statuses
$.ajax({url:"get_status_ajax.php", success: UpdateServerStatus, cache:false});
}
function StartUp()
{
setTimeout(GetServerStatus, 5*60*1000);
}
$(document).ready(StartUp);
</script>
get_status_ajax.php
<?php
include_once('status_funcs.php');
echo GetStatus();
?>
status_funcs.php
<?php
function GetStatus()
{
$status = "";
// Get statuses
$query = "SELECT server_name, server_status FROM servers;"
$result = mysql_query($query);
while ($server = mysql_fetch_array($result))
{
$status .= echo '<li>'.$server['server_name']. - '.$server['server_status'].'</li>';
}
return $status;
}
?>
Now the page is filled in at page load so it will give the right info without any updating at all. No extra initial AJAX call either so a win-win situation.
I am returning an HTML string which is fine for such a simple feature that only this site uses. For more complicated things or public APIS it’s probably better to return an array from the function instead then convert and pass it via JSON (for AJAX) or directly (for your initial page load). The processing would be duplicated between server-side language and the javascript, but it adds the flexibility that you (and more importantly your API consumers) need. Don’t be tempted to just pass a JSON string at page load and fill it in via Javascript. You regain points 2 and 3 of the bad solution by doing that.
End
It’s minimal extra code. Both new versions use the same AJAX and timing code so that can be ignored in a comparison. By adding one function you get a much more solid experience for the user. Also you don’t run the risk of updating one version of the code and not the other giving an inconsistent status. This is especially handy when more than one person is working on the code as the one doing output code can treat the internal API as a black box and not have to look inside it to see what it is doing.
There are occasional reasons that things need to be loaded after the initial page load such as if the browser properties (dimensions, capabilities, etc) are vital. I used Google Maps in my example on the earlier post although even there a small map could be pre-loaded to get it up and running as fast as possible while it loads the parts around it.
Is there any sane reason not to do it this way?
Is there a better way still?
Let us all in on the secret in the comments!
Read more from the Coding, Uncategorized, Webdev category. If you would like to leave a comment, click here: Comment. or stay up to date with this post via RSS, or you can
Trackback from your site.
Social Bookmark :
Technorati,
Digg,
de.licio.us,
Yahoo,
Blinkbits,
Blogmarks,
Google,
Magnolia.
Leave a Comment
If you would like to make a comment, please fill out the form below.
