Migrating Web-Based PHP Applications to Ajax
Web development is messy. Over the years, our toolbox has filled up with odd implements that are hard to use and don't fit well together. Web code has become a legacy problem. A typical web page is a tangle of HTML, JavaScript, and server-side scripts. User interface logic is interwoven with business rules and client-server communications. In most programming environments, we use documented APIs, so you just pass arguments to a function and get back the results. In the web environment, we've typically needed hacks like populating hidden fields in a form, and regenerating the whole page, even for a tiny change. Can we make the process more rational?
This article describes a makeover of a typical database-backed web form. We'll show some old code – a mixture of HTML, JavaScript, and PHP – and rebuild it with modern web techniques like Ajax, and modern tools like jQuery. The benefits will include:
- Separating dynamic content from static content.
- Separating content, style, and processing.
- Web client-server communication via function calls.
- Partial page updates instead of flash-bang page reloads.
- Faster development and more maintainable code.
- Faster load times and improved caching.
The Old Grey Web
In the beginning was HTML, shortly followed by forms, client-side JavaScript, and server-side CGI scripts. You would fill in form variables and submit the form to the CGI script, or generate a long GET-style URL in JavaScript. JavaScript was close to undebuggable. Core variables like window were not part of the language, and the Microsoft-Netscape browser wars introduced gratuitous differences that continue to plague us. Things got a little better over time, as the W3C defined the DOM and developers built cross-platform DHTML libraries.
Some dynamic pages are easy, you query the database once and dump some nicely formatted HTML, but most are harder. Database values populate pull-down menus (HTML select and option tags) and choices (radio and checkbox tags). Some forms cover multiple pages, and the logic for maintaining overall state gets trickier. Every form submission returns a fully regenerated page, maintaining all the state of the previous submissions.
Traditional options on how to structure the application include:
- Put everything in a single script. On the first call, query the database and generate HTML and form elements. On subsequent calls, the form passes a command (such as
add,change, ordelete) and any arguments (such as the person's ID, ifchangeordelete). The script modifies the database and again rewrites the page's content. The form elements must reflect the database changes. - Use two scripts. The first queries the database and generates the HTML, with two differences from the previous example:
- The form has an iframe for displaying dynamic contents.
- The form's
actionis the second script. - The form's
targetis the name of the iframe.
Now the form is generated once and its elements maintain their values across form submissions. The output of the second script is displayed in the iframe.
The second option sounds better, but it still has problems: if an action changed the data underlying the form elements in the enclosing page, the whole page needs to be regenerated. We need a third option.
The New Toolbox
The common restriction is the whole-page design. No matter what you do, no matter how small the change, you submit it to the server and get back a brand new page (an iframe is like a mini-page, and its size can't be changed). A variety of JavaScript remoting techniques have tried to make the web client-server connection work more like a procedure call. I think the most useful contributions are:
- innerHTML
- XMLHttpRequest
- Ajax
- JSON
- New JavaScript libraries
innerHTML- This DOM attribute, introduced by Microsoft in IE4, is a de facto (not official W3C) standard supported by all modern browsers. With it, you get and set the contents between any HTML start and end tags, without a page refresh. This chunk of HTML:
<p id="changeme">Now you see it.</p>
can be modified by this chunk of JavaScript:
var obj = document.getElementById("changeme"); obj.innerHTML = "Now you don't";to produce this:
<p id="changeme">Now you don't</p>
Although the DOM has functions to change page elements dynamically,
innerHTMLis simpler and faster. Unfortunately,innerHTMLis a read-only attribute for many table elements in IE, forcing use of alternatives. XMLHttpRequest- This is an API to send client requests to the server and receive the server responses. Microsoft invented it to make Outlook Web Access work more like a desktop application. It was included in IE5 and is supported by all modern browsers. Combined with
innerHTML, you can call a server script and use the data returned to update any element on the page. Despite its utility, this function was hidden in plain sight for years (you wouldn't find it in JavaScript or DHTML books). Things changed when Google Mail, Maps, and Suggest demonstrated its responsiveness and introduced a new web application model. - Ajax
- The dam burst in 2005, when Jesse James Garrett named the new model Ajax (Asynchronous JavaScript and XML), comprising
XMLHttpRequest, the DOM, and other techniques. The brand and its timing were perfect, rejuvenating web development and rehabilitating JavaScript. - JSON
- The old remoting frameworks used various formats for the client-server data stream, and
XMLHttpRequest, as the name suggested, used XML. Doug Crockford designed the light and simple JSON (JavaScript Object Notation) format. It's trivial to parse JSON into JavaScript data structures (justeval(json_string)), and faster than deconstructing XML. - JavaScript libraries
- High-quality JavaScript libraries have been developed to simplify new-model web development and bridge the inevitable cross-browser issues. I've chosen John Resig's jQuery over worthy competitors like Prototype, Dojo, or YUI for a number of reasons:
- Compactness: About 19KB compressed
- Chainability: Calls can be chained for more compact code
- Support: Good documentation and development community
- Flexibility: Its selector syntax includes CSS 1-3 and XPath phrases to select page elements
- Architecture: Easy to extend through add-ons and plugins
The Makeover
History class is over, and beauty class begins.
To make the presentation clear and short, our code examples ignore errors and possible security issues. The purpose is to show how jQuery and Ajax techniques can improve an old script. For production use, you would check function error returns, untaint input data, and follow the other rules of good web hygiene. With the new Ajax methods, an error in the client or server code can cause a silent failure. FireBug is a very handy tool for developing and debugging Ajax applications. The full-featured version is a Firefox plugin, but a light version is available for IE and other browsers.
Let's define our form's requirements:
- Get data from a
peopletable in a database. Theidcolumn is the primary key. - Display the names in a pull-down menu (a form
selectelement). - Let the user select a person from the menu.
- Display information about that person in a table: first name, last name, favorite dance, and favorite pie.
This example assumes the number of people will fit in an HTML select element without killing the browser. Larger data would require a paged table or something similar. The page should look something like this:
People
| First Name | First Name | Dance | Pie |
| Alfredo | de Darc | tango | blueberry |
Version 1: Original Code
In the original version we do everything in a single PHP script: write the static HTML, create the original list of people, and fill in the lower table if a person had been selected.
people1.php:
<?php
$cmd = @$_REQUEST["cmd"];
$id = @$_REQUEST["id"];
mysql_connect($server, $user, $password);
mysql_select_db("test");
?>
<html>
<head><title>Old Form</title>
<script>
// Get the selected user and retrieve his/her info
function user_info(sel)
{
var opt = sel.options;
var user_id = opt[sel.selectedIndex].value;
// Construct a GET URL, or create a hidden field for "cmd"
var url = "people1.php?cmd=info&id=" + user_id;
window.location.href = url;
}
</script>
</head>
<body>
<form action="people1.php" method="post">
People<br>
<select name="people" onchange="user_info(this)">
<option value="">(select a person)
<?php
// Get all users and display every time script is called
$result = mysql_query("select id, fname, lname from people");
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
echo "<option value='$row[id]'",
$row["id"] == $id ? " selected" : "",
">$row[fname] $row[lname]\n";
?>
</select>
<?php
if ($cmd == "info")
{
$id = mysql_real_escape_string($id);
$result = mysql_query("select * from people where id='$id'");
$info = mysql_fetch_array($result, MYSQL_ASSOC);
}
else
$info = array("fname"=>" ", "lname"=>" ", "dance"=>" ", "pie"=>" ");
echo <<< END
<br>
<table border=1>
<tr>
<td>First Name</td><td>Last Name</td><td>Dance</td><td>Pie</td></tr>
<tr>
<td>$info[fname]</td>
<td>$info[lname]</td>
<td>$info[dance]</td>
<td>$info[pie]</td>
</tr>
</table>
END;
?>
</form>
</body>
New Design
Here's the plan:
- Split the initial script into three files: static content (HTML), client-side processing (JavaScript), and server-side processing (PHP).
- Include jquery.js and the new JavaScript file in the HTML file.
- Add a unique
idattributes to all dynamic content tags. - Define JavaScript functions to make Ajax-style server calls and update page elements.
We can merge the dynamic and static content in one of two ways:
- PHP returns the dynamic content as HTML, and JavaScript stuffs it into a page element with
innerHTML. - PHP returns the dynamic content in a JSON-format data array, and JavaScript builds and inserts the HTML into the target page element.
Let's try both.
Version 2: Ajax Submit, HTML Return
In this method, PHP generates the HTML for the option tags, and we just stuff the HTML into the enclosing select. Now we start with an HTML file, and it's just a container:
people2.html: <html> <head><title>New Form Version 1</title> <script src="jquery.js"></script> <script src="people2.js"></script> </head> <body> People<br> <select id="people"> </select> <br> <table border=1> <thead> <tr><td>First Name</td><td>Last Name</td><td>Dance</td><td>Pie</td></tr> </thead> <tbody id="info"> <tr> <td> </td> <td> </td> <td> </td> <td> </td> </tr> </tbody> </table> </body> </html>
Now let's look at the JavaScript file. jQuery's central function is $(), which returns a jQuery object. Its arguments may have many forms, but we'll use these now:
$(document): Selects the DOMdocumentobject.$("#myid"): Selects the element withid="myid".
The jQuery call $(document).ready replaces window.onload. It's called when the DOM is ready, instead of waiting for all images to load. This avoids synchronization problems. All of the JavaScript functions that work with form elements can be in here.
The load() function calls the server script people.php and inserts its output into the select element with id people. This is the equivalent of the first chunk of the original version. It makes an Ajax connection to the server, and inserts the returned HTML into the element with id people:
(I've spread out the formatting to help distinguish the parentheses and curly brackets.)
people2.js:
$(document).ready
(
function()
{
// Call this when the DOM is ready:
$("#people").load("people2.php?cmd=init");
// Call this when a person is selected:
$("#people").change(function()
{
// get the user's id from the selected option:
var user_id = $(":selected").val();
$("#info").load("people2.php?cmd=info&id=" + user_id);
});
}
);
For this version, that's all the JavaScript we need. By the way, this approach has been called AHAH (Asynchronous HTML and HTTP). Now we'll look at the PHP script it calls. This version is like the original, but it only prints the HTML fragment for the current query:
people2.php:
<?php
$cmd = @$_REQUEST["cmd"];
$id = @$_REQUEST["id"];
mysql_connect($server, $user, $password);
mysql_select_db("test");
if ($cmd == "init")
{
$result = mysql_query("select id, fname, lname from people");
echo "<option value=''>(select a person)\n";
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
echo "<option value='$row[id]'>$row[fname] $row[lname]\n";
}
elseif ($cmd == "info")
{
$id = mysql_real_escape_string($id);
$result = mysql_query("select * from people where id='$id'");
$info = mysql_fetch_array($result, MYSQL_ASSOC);
echo <<< END
<tr>
<td>$info[fname]</td>
<td>$info[lname]</td>
<td>$info[dance]</td>
<td>$info[pie]</td>
</tr>
END;
}
?>
Although you could call this approach AJAJ, thankfully no one does.
Version 3: Ajax Submit, JSON Return
In this alternative, PHP builds a data array from the database query and returns it to JavaScript in JSON format. jQuery converts this JSON string into a jQuery object and passes it to a callback function, which builds the HTML for the options and inserts it into the appropriate page element. This example is the same as people2.html, except it calls people3.js:
people3.html: <html> <head><title>New Form Version 1</title> <script src="jquery.js"></script> <script src="people3.js"></script> </head> <body> People<br> <select id="people"> </select> <br> <table border=1> <thead> <tr><td>First Name</td><td>Last Name</td><td>Dance</td><td>Pie</td></tr> </thead> <tbody id="info"> <tr> <td> </td> <td> </td> <td> </td> <td> </td> </tr> </tbody> </table> </body> </html>
We're just going to fill the container in a different way. In this version, we call getJSON with three arguments:
- The URL
- A dictionary of name:value pairs
- A JavaScript callback function.
A GET URL will be built from the URL and name:value arguments, and the returned JSON string will be converted into a JavaScript object and passed to the callback function.
people3.js:
$(document).ready
(
function()
{
// Call this when the DOM is ready:
$.getJSON("people3.php",
{ cmd : "init" },
make_menu);
// Call this when a person is selected:
$("#people").change(function()
{
var user_id = $(":selected").val();
$.getJSON("people3.php",
{ cmd : "info", id: user_id },
make_info);
});
}
);
function make_menu(obj)
{
var str = "";
var len = obj.length;
str += "<option value=''>(select a person)\n";
for (var i = 0; i < len; i++)
{
var user = obj[i];
str += "<option value='" + user["id"] + "'>" +
user["fname"] + " " +
user["lname"] + "\n";
}
$("#people").html(str);
}
function make_info(info)
{
var str = "<tr>";
// You can get each value as info.name or info["name"].
// Let's get the first name using the first way.
str += "<td>" + info.fname + "</td>";
str += "<td>" + info["lname"] + "</td>";
str += "<td>" + info["dance"] + "</td>";
str += "<td>" + info["pie"] + "</td>";
str += "</tr>\n";
$("#info").html(str);
}
This version of the PHP script is even simpler than people2.php. Instead of creating HTML from the database return values, we just encode the PHP data array in JSON format and send it off:
people3.php:
<?php
$cmd = @$_REQUEST["cmd"];
$id = @$_REQUEST["id"];
mysql_connect($server, $user, $password);
mysql_select_db("test");
if ($cmd == "init")
{
$result = mysql_query("select id, fname, lname from people");
$user_array = array();
while($row = mysql_fetch_array($result, MYSQL_ASSOC))
$user_array[] = $row;
echo json_encode($user_array);
}
elseif ($cmd == "info")
{
$id = mysql_real_escape_string($id);
$result = mysql_query("select * from people where id='$id'");
$info = mysql_fetch_array($result, MYSQL_ASSOC);
echo json_encode($info);
}
?>
The json_encode function is included with standard PHP starting with version 5.2. For earlier versions, see the PHP JSON manual section.
If you chose our friend Alfredo from the menu, the JSON returned would look like this:
{"id":"1","fname":"Alfredo","lname":"de Darc","dance":"tango","pie":"blueberry"}
That JSON string is converted to a JavaScript object by jQuery and passed to the make_menu function as the info argument.
Judging the Makeover
The main choice between these new versions is where to do the output formatting: in PHP (version 2) or in JavaScript (version 3). Another factor might be what other plans you have for the data. Instead of throwing info away after generating the HTML in version 3, you could save it in a global JavaScript variable and use it for other purposes later.
Are these new versions better than the original? Let's see if our original promises were kept:
- Separating dynamic content from static content.
- All the static content is in the HTML file, and the dynamic data from the PHP script are processed in JavaScript for page placement.
- Separating content, style, and processing.
- We didn't show it here, but a separate CSS file would be a nice orthogonal addition.
- Web client-server communication via function calls.
- Good old Ajax.
- Partial page updates instead of flash-bang page reloads.
- Ajax again.
- Faster development and more maintainable code.
- PHP now just gets data from the database and returns output chunks (HTML or JSON) rather than whole pages.
- Faster load times and improved caching.
people2.html,people2.js,people3.html, andpeople3.jsare static files that will be cached by the browser (and the web server, which will make the application more scalable). The whole output page is also cached, the only changes being performed by JavaScript within the browser. Finally, we avoid a database lookup in every call topeople2.phporpeople3.phpafter the first one.
The most important benefit is that the new approach will scale much better with future requirements, such as adding a new person or editing the data of an existing person. And new requirements are as sure as death and taxes.






























































