Tuesday, July 14, 2009

AJAX and PHP Building Responsive Web Applications by Cristian Darie, Bogdan Brinzarea Chapter 3

Server-Side Techniques with PHP and MySQL

If AJAX is mainly about building smarter clients, then the servers these clients talk to must be equally smart, otherwise they won't get along very well for too long.

In Chapter 2, you only read static text or XML files from the server. In this chapter, we start putting the server side to work, with PHP to generate dynamic output, and MySQL to manipulate and store the back-end data. In this chapter, you will learn how to:

• Use PHP to perform functionality on the server side
• Let clients communicate with the server by passing parameters
• Use XML on the client and the server
• Use PHP scripts to avoid potential JavaScript security problems
• Perform repetitive tasks in your client
• Work with MySQL databases
• Optimize your application's architecture

PHP and DOM
In Chapter 2, you read data asynchronously from the server. While the mechanism is pretty standard and you will use the same routines many times in this book, what's unusual is that the data passed back from the server was a static file (either text or XML).

In most real-world situations, you will need the server to do some processing, and generate some dynamic output. In this book, we will use PHP to do the server-side part of the job. If your background in PHP isn't strong, an online search for "php tutorial" will generate lots of interesting resources, including the official PHP tutorial at http://php.net/tut.php. If you enjoy learning by practicing, you may want to check out one of Cristian Darie and Mihai Bucica's e-commerce books, such as Beginning PHP 5 and MySQL E-Commerce: From Novice to Professional.

You can even use the Suggest and Autocomplete application that you will build in Chapter 6, which finds the help page of the PHP functions for you. You will find the application at http://ajaxphp.packtpub.com/ajax/suggest/.

In the first exercise for this chapter, you will write a PHP script that uses the PHP's DOM
functions to create XML output that will be read by the client. PHP's DOM functionality is similar to JavaScript's DOM functionality, and its official documentation can be found at http://www.php.net/manual/en/ref.dom.php.

The XML document you will create on the server will be almost the same as the XML document you saved as a static XML file in Chapter 2, but this time it will be generated dynamically:

<response>
<books>
<book>
<title>Building Reponsive Web Applications with AJAX and PHP</title>
<isbn>1-904811-82-5</isbn>
</book>
</books>
</response>

Time for Action—Doing AJAX with PHP
1. In the foundations folder create a subfolder called php.
2. In the php folder create a file named phptest.html, and add the following text to it:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Using the PHP DOM</title>
<script type="text/javascript" src="phptest.js"></script>
</head>
<body onload="process()"> The AJAX book of 2006 is:
<br />
<div id="myDivElement" />
</body>
</html>

3. The client-side code, phptest.js, is almost identical to books.js from the XML
exercise in Chapter 2. The changed bits are highlighted:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older try
{
// try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works

for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// read a file from the server function process()
{
// only continue if xmlHttp isn't void if (xmlHttp)
{
// try to connect to the server try
{
// initiate reading a file from the server xmlHttp.open("GET", "phptest.php", true); xmlHttp.onreadystatechange = handleRequestStateChange; xmlHttp.send(null);
}
// display the error in case of failure
catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleRequestStateChange()
{
// when readyState is 4, we are ready to read the server response
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server handleServerResponse();
}
catch(e)
{
// display error message
alert("Error reading the response: " + e.toString());
}
}
else
{
// display status message
alert("There was a problem retrieving the data:\n" +
xmlHttp.statusText);

}
}
}

// handles the response received from the server function handleServerResponse()
{
// read the message from the server
var xmlResponse = xmlHttp.responseXML;
// catching potential errors with IE and Opera
if (!xmlResponse || !xmlResponse.documentElement)
throw("Invalid XML structure:\n" + xmlHttp.responseText);
// catching potential errors with Firefox
var rootNodeName = xmlResponse.documentElement.nodeName;
if (rootNodeName == "parsererror") throw("Invalid XML structure");
// obtain the XML's document element xmlRoot = xmlResponse.documentElement;
// obtain arrays with book titles and ISBNs titleArray = xmlRoot.getElementsByTagName("title"); isbnArray = xmlRoot.getElementsByTagName("isbn");
// generate HTML output var html = "";
// iterate through the arrays and create an HTML structure
for (var i=0; i<titleArray.length; i++)
html += titleArray.item(i).firstChild.data +
", " + isbnArray.item(i).firstChild.data + "<br/>";
// obtain a reference to the <div> element on the page myDiv = document.getElementById("myDivElement");
// display the HTML output
myDiv.innerHTML = html;
}

4. And finally, the phptest.php file:
<?php
// set the output content type as xml header('Content-Type: text/xml');
// create the new XML document
$dom = new DOMDocument();

// create the root <response> element
$response = $dom->createElement('response');
$dom->appendChild($response);

// create the <books> element and append it as a child of <response>
$books = $dom->createElement('books');
$response->appendChild($books);

// create the title element for the book
$title = $dom->createElement('title');
$titleText = $dom->createTextNode
('Building Reponsive Web Applications with AJAX and PHP');
$title->appendChild($titleText);

// create the isbn element for the book
$isbn = $dom->createElement('isbn');
$isbnText = $dom->createTextNode('1-904811-82-5');
$isbn->appendChild($isbnText);

// create the <book> element
$book = $dom->createElement('book');
$book->appendChild($title);
$book->appendChild($isbn);

// append <book> as a child of <books>
$books->appendChild($book);

// build the XML structure in a string variable
$xmlString = $dom->saveXML();
// output the XML string
echo $xmlString;
?>

5. First let's do a simple test to see what phptest.php returns. Load http://localhost/ajax/foundations/php/phptest.php in your web browser to ensure it generates a well-formed XML structure:

Figure 3.1: Simple XML Structure Generated by PHP

If you don't get the expected result, be sure to check not only the code, but also your PHP
installation. See Appendix A for details about how to correctly set up your machine.

6. Once you know the server gives back the right response, you can test the whole solution by loading http://localhost/ajax/foundations/php/phptest.html:

Figure 3.2: AJAX with PHP

What Just Happened?
When it comes to generating XML structures, not only on the client side but on the server side as well, you have to choose between creating the XML document using the DOM, or by joining strings. Your PHP script, phptest.php, starts by setting the content output to text/xml:

<?php
// set the output content type as xml
header('Content-Type: text/xml');

The PHP documentation for header is http://www.php.net/manual/en/function.header.php (remember, you can simply search for 'header' in the Suggest application, and it will direct you to the help page).

While in JavaScript files we use double quotes for strings, in PHP we will always try to use single quotes. They are processed faster, they are more secure, and they are less likely to cause programming errors. Learn more about PHP strings at http://php.net/types.string. You can find two useful articles on PHP strings at http://www.sitepoint.com/print/quick- php-tips and http://www.jeroenmulder.com/weblog/2005/04/php_single_and_ double_quotes.php.

The PHP DOM, not very surprisingly, looks a lot like the JavaScript DOM. It all begins by creating a DOM document object, which in PHP is represented by the DOMDocument class:

// create the new XML document
$dom = new DOMDocument();

Then you continue by creating the XML structure using methods such as createElement,
createTextNode, appendChild, and so on:

// create the root <response> element
$response = $dom->createElement('response');
$dom->appendChild($response);

// create the <books> element and append it as a child of <response>
$books = $dom->createElement('books');
$response->appendChild($books);
...

In the end, we save the whole XML structure as a string, using the saveXML function, and echo the string to the output.

$xmlString = $dom->saveXML();
// output the XML string echo $xmlString;
?>

The XML document is then read and displayed at the client side using techniques that you came across in Chapter 2.

In most cases, you will generate XML documents on the server, and will read them on the client, but of course you can do it the other way round. In Chapter 2, you saw how to create XML documents and elements using JavaScript's DOM. You can then pass these structures to PHP (using GET or POST as you will see in the following exercise). To read XML structures from PHP you can also use the DOM, or you can use an easier-to-use
API called SimpleXML. You will practice using SimpleXML in Chapter 9, when building your RSS Reader application.

Passing Parameters and Handling PHP Errors
The previous exercise with PHP ignores two very common aspects of writing PHP scripts:

• You usually need to send parameters to your server-side (PHP) script.
• Now that the client side is quite well protected, you should implement some error-handling technique on the server side as well.

You can send parameters to the PHP script using either GET or POST. Handling PHP errors is done with a PHP-specific technique. In the following exercise, you will pass parameters to a PHP script, and implement an error-handling mechanism that you will test by supplying bogus values. The application will look as shown in Figure 3.3.

This page will make an asynchronous call to a server, asking the server to divide two numbers for you. The server, when everything works well, will return the result as an XML structure that looks like this:

<?xml version="1.0"?>
<response>1.5</response>

In the case of a PHP error, instead of generating an XML string, the server script returns a plain text error message, which is intercepted by the client (after doing the exercise, you will understand why).

Time for Action—Passing PHP Parameters and Error Handling
1. In the foundations folder, create a new folder called morephp.
2. In the morephp folder, create a file named morephp.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: PHP Parameters and Error Handling</title>
<script type="text/javascript" src="morephp.js"></script>
</head>
<body>
Ask server to divide
<input type="text" id="firstNumber" />
by
<input type="text" id="secondNumber" />
<input type="button" value="Send" onclick="process()" />
<div id="myDivElement" />
</body>
</html>

3. Create a new file named morephp.js:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older try
{
// try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// read a file from the server function process()
{
// only continue if xmlHttp isn't void if (xmlHttp)
{
// try to connect to the server try
{
// get the two values entered by the user
var firstNumber = document.getElementById("firstNumber").value;
var secondNumber = document.getElementById("secondNumber").value;
// create the params string
var params = "firstNumber=" + firstNumber +
"&secondNumber=" + secondNumber;
// initiate the asynchronous HTTP request xmlHttp.open("GET", "morephp.php?" + params, true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}
// display the error in case of failure catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleRequestStateChange()
{
// when readyState is 4, we are ready to read the server response
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server handleServerResponse();
}
catch(e)
{
// display error message
alert("Error reading the response: " + e.toString());
}
}
else
{
// display status message
alert("There was a problem retrieving the data:\n" +
xmlHttp.statusText);
}
}
}

// handles the response received from the server function handleServerResponse()
{
// retrieve the server's response packaged as an XML DOM object var xmlResponse = xmlHttp.responseXML;
// catching potential errors with IE and Opera
if (!xmlResponse || !xmlResponse.documentElement)
throw("Invalid XML structure:\n" + xmlHttp.responseText);

// catching potential errors with Firefox
var rootNodeName = xmlResponse.documentElement.nodeName;
if (rootNodeName == "parsererror")
throw("Invalid XML structure:\n" + xmlHttp.responseText);
// getting the root element (the document element)
xmlRoot = xmlResponse.documentElement;
// testing that we received the XML document we expect if (rootNodeName != "response" || !xmlRoot.firstChild)
throw("Invalid XML structure:\n" + xmlHttp.responseText);
// the value we need to display is the child of the root <response>
element
responseText = xmlRoot.firstChild.data;
// display the user message
myDiv = document.getElementById("myDivElement");
myDiv.innerHTML = "Server says the answer is: " + responseText;
}

4. Create a file called morephp.php:
<?php
// load the error handling module require_once('error_handler.php');
// specify that we're outputting an XML document header('Content-Type: text/xml');
// calculate the result
$firstNumber = $_GET['firstNumber'];
$secondNumber = $_GET['secondNumber'];
$result = $firstNumber / $secondNumber;
// create a new XML document
$dom = new DOMDocument();
// create the root <response> element and add it to the document
$response = $dom->createElement('response');
$dom->appendChild($response);
// add the calculated sqrt value as a text node child of <response>
$responseText = $dom->createTextNode($result);
$response->appendChild($responseText);
// build the XML structure in a string variable
$xmlString = $dom->saveXML();
// output the XML string echo $xmlString;
?>

5. Finally, create the error-handler file, error_handler.php:
<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);
// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts
exit;
}
?>

6. Load http://localhost/ajax/foundations/morephp/morephp.html and play with it.

Figure 3.3: PHP Parameters and Error Handling

What Just Happened?
You must be familiar with almost all the code on the client side by now, so let's focus on the server side, where we have two files: morephp.php and error_handler.php.

The morephp.php file is expected to output the XML structure with the results of the number division. However, it starts by loading the error-handling routine. This routine is expected to catch
any errors, create a better error message than the default one, and send the message back to the client.

<?php
// load the error handling module
require_once('error_handler.php');

PHP 5 does support exceptions like the other OOP languages. However, with PHP 5, you are limited to using exception objects that you throw and catch yourself, and they can help when building a large architecture where they can improve your code. PHP's core doesn't generate exceptions when something bad happens. Probably because of backward compatibility reasons, when a problem happens, instead of throwing exceptions, PHP 5 generates errors, which represent a much more primitive way to handle run-time problems. For example, you can't catch an error, deal with it locally, and then let the script continue normally, as you can do with exceptions. Instead, to deal with errors, the best you can do is to specify a function to execute automatically; this function is called before the script dies, and offers you a last chance to do some final processing, such as logging the error, closing database connections, or telling your visitor something "friendly".

In our code, the error_handler.php script is instructed to handle errors. It simply receives the error, and transforms the error message into something easier to read than the default error message. However, note that error_handler.php catches most errors, but not all! Fatal errors cannot be trapped with PHP code, and they generate output that is out of the control of your program. For example, parse errors, which can happen when you forget to write the $ symbol in the front of a variable name, are intercepted before the PHP code is executed; so they cannot be caught with PHP code, but they are logged in the Apache error log file.

It is important to keep an eye on the Apache error log when your PHP script behaves strangely. The default location and name of this file is Apache2\logs\error.log, and it can save you from many headaches.

After setting the error-handling routine, we set the content type to XML, and divide the first received number by the second number. Note the usage of $_GET to read the variables sent using GET. If you sent your variables using POST you should have used $_POST. Alternatively, you can use $_REQUEST, which finds variables sent with any method (including cookies); but it is generally recommended to avoid using it because it is a bit slower than the others.

// specify that we are outputting an XML document header('Content-Type: text/xml');
// calculate the result
$firstNumber = $_GET['firstNumber'];
$secondNumber = $_GET['secondNumber'];
$result = $firstNumber / $secondNumber;

The division operation will generate an error if $secondNumber is 0. In this case, we expect the error-handler script to intercept the error. Note that in a real-world the situation, the professional way would be to check the value of the variable before calculating the division, but in this case we are interested in checking the error-handling script.

After calculating the value, you package it into a nice XML document and output it, just as in the previous exercise:

// create a new XML document
$dom = new DOMDocument();
// create the root <response> element and add it to the document
$response = $dom->createElement('response');
$dom->appendChild($response);
// add the calculated sqrt value as a text node child of <response>
$responseText = $dom->createTextNode($result);
$response->appendChild($responseText);
// build the XML structure in a string variable
$xmlString = $dom->saveXML();
// output the XML string echo $xmlString;
?>

Let's now have a look at the error-handling script—error_handler.php. This file has the role of intercepting any error messages generated by PHP, and outputting an error message that makes sense, and can be displayed by your JavaScript code:

Figure 3.4: Good Looking Error Message

Without the customized error handler, the error message you will get would be:

Figure 3.5: Bad Looking Error Message

The error message will look like Figure 3.5 if the display_errors option in php.ini is On. By default, that option is Off and the errors are logged just in the Apache error log, but while writing code it may help to make them be displayed as well. If the code was production code, both error messages would have been inappropriate. You should never show such debugging information to your end users.

So what happens in error_handler.php? First, the file uses the set_error_handler function to establish a new error-handling function:

<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);

When an error happens, we first call ob_clean() to erase any output that has already been generated—such as the <response></response> bit from Figure 3.5:

// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated if(ob_get_length()) ob_clean();

Of course, if you prefer to decide to keep those bits when doing certain debugging things,
you can comment out the ob_clean() call. The actual error message is built using the system variables $errNo, $errStr, $errFile, and $errLine, and the carriage return is generated using the chr function.

// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts
exit;
}
?>

The error-handling scheme presented is indeed quite simplistic, and it is only appropriate while writing and debugging your code. In a production solution, you need to show your end user a friendly message without any technical details. If you want to package the error details as an XML document to be read on the client, keep in mind that parse and fatal errors will not be processed by your function, and will behave as set up in PHP's configuration file (php.ini).

This case also presents the scenario where the user can attempt to make several server requests at the same time (you can do this by clicking the Send button multiple times quickly enough). If you try to make a request on a busy XMLHttpRequest object, its open method generates an exception. The code is well protected with try/catch constructs, but the error message doesn't look very
user-friendly as shown in Figure 3.6.

Figure 3.6: Request on a Busy XMLHttpRequest

This message might be just what you need, but in certain circumstances you may prefer to react differently to this kind of error than with other kinds of errors. For example, in a production scenario, you may prefer to display a note on the page, or display a friendly "please try again later" message, by modifying the process() function as shown in the following code snippet:

// read a file from the server function process()
{
// only continue if xmlHttp isn't void
if (!xmlHttp) return;
// don't try to make server requests if the XMLHttpObject is busy if !(xmlHttp.readyState == 0 || xmlHttp.readyState == 4)
alert("Can't connect to server, please try again later.");
else
{
// try to connect to the server try
{
// get the two values entered by the user
var firstNumber = document.getElementById("firstNumber").value;
var secondNumber = document.getElementById("secondNumber").value;
// create the params string
var params = "firstNumber=" + firstNumber + "&secondNumber=" + secondNumber;
// initiate the asynchronous HTTP request xmlHttp.open("GET", "morephp.php?" + params, true); xmlHttp.onreadystatechange = handleRequestStateChange; xmlHttp.send(null);
}

// display the error in case of failure catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

The exact way you handle these errors can vary depending on the scenario. During the course of this book, you will see more solutions in action:

• Sometimes you may prefer to simply ignore these errors.
• Other times you will display a custom error message as shown in the code above.

In most cases you will try to avoid getting the errors in the first place—it is always better to prevent a problem than to handle it after it happened. For example, there are several ways to avoid getting "connection busy"-type errors, which happen when you try to make a server request using an XMLHttpRequest object that is still busy processing a previous request:

• You could open a new connection (create a new XMLHttpRequest object) for every message you need to send to the server. This method is easy to implement and it can be helpful in many scenarios, but we'll generally try to avoid it because it can affect the server's performance (your script continues to open connections and initiate requests even if the server hasn't finished answering older requests), and it doesn't guarantee that you receive the responses in the same order as you made the calls (especially if the server is busy or the network is slow).
• You could record the message in a queue and send it later when the connection becomes available (you will see this method in action in several exercises of this book, including the AJAX Form Validation, and the AJAX Chat).
• You can ignore the message altogether if you can implement the code in such a way that it would not attempt to make multiple requests over the same connection, and use the existing error-handling code.

Connecting to Remote Servers and JavaScript
Security
You may be surprised to find out that the PHP exercises you have just completed worked smoothly because the server (PHP) scripts you called asynchronously were running on the same server from which the HTML file was loaded.

Web browsers have very strict (and different) ways to control what resources you can access from the JavaScript code. If you want to access another server from your JavaScript code, it is safe to
say that you are in trouble. And this is what we will do in the exercise that follows; but before that, let's learn a bit of theory first.

So, the JavaScript code runs under the security privileges of its parent HTML file. By default, when you load an HTML page from a server, the JavaScript code in that HTML page will be allowed to make HTTP requests only to that server. Any other server is a potential enemy, and (unfortunately) these enemies are handled differently by each browser.

Internet Explorer is a friendly kind of web browser; which means that is arguably less secure, but more functional. It has a security model based on zones. The four zones are Internet, Local intranet, Trusted sites, and Restricted sites. Each zone has different security settings, which you can change going to Tools | Internet Options | Security. When accessing a web resource, it will be automatically assigned to one of the security zones, and the specific security options will be applied.

The default security options may vary depending on your system. By default, Internet Explorer will give full privileges to scripts loaded from a local file resource (not through a web server, not even the local web server). So if you try to load c:\ajax\... the script will run smoothly (before execution, you may be warned that the script you are loading has full privileges). If the JavaScript code was loaded through HTTP (say, http://localhost/ajax/..../ping.html), and that JavaScript code tries to make an HTTP request to another server, Internet Explorer will automatically display a confirmation box, where the user is asked to give permission for that action.

Firefox and Mozilla-based browsers have a more restrictive and more complicated security model, based on privileges. These browsers don't display a confirmation window automatically; instead, your JavaScript code must use a Mozilla specific API to ask about performing the required
actions. If you are lucky the browser will display a confirmation box to the user, and depending on user's input, it will give the permission (or not) to your JavaScript code. If you aren't lucky, the
Mozilla-based browser will ignore your code request completely. By default, Mozilla-based
browsers will listen to privilege requests asked from local (file:///) resources, and will ignore completely requests from scripts loaded through HTTP, unless these scripts are signed (these are the default settings that can be changed manually, though). Learn more about signing scripts for Mozilla browsers at http://www.mozilla.org/projects/security/components/
signed-scripts.html.

In the next exercise, you'll create a JavaScript program that reads random numbers from the online service http://www.random.org. This site provides an online web service that generates truly random numbers. The page that explains how to access the server through HTTP is located at http://www.random.org/http.html. When writing programs for this purpose, you should check the guidelines mentioned at: http://www.random.org/guidelines.html. Finally, to get a feeling about what random numbers look like, feel free to load http://www.random.org/cgi-bin/randnum in your web browser (when called with no options, by default it generates 100 random numbers
between 1 and 100). Our client will ask for one random number between 1 and 100 at a time, by making a request to http://www.random.org/cgibin/randnum?num=1&min=1&max=100.

Figure 3.7: Connecting to Remote Servers

Time for Action—Connecting to Remote Servers
1. Start by creating a new subfolder of the foundations folder, called ping.
2. In the ping folder, create a new file named ping.html with the following contents:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Connecting to Remote Servers</title>
<script type="text/javascript" src="ping.js"></script>
</head>
<body onload="process()">
Server, tell me a random number!<br/>
<div id="myDivElement" />
</body>
</html>

3. Create a new file named ping.js with the following code:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// holds the remote server address and parameters
var serverAddress = "http://www.random.org/cgi-bin/randnum";
var serverParams = "num=1" + // how many random numbers to generate "&min=1" + // the min number to generate "&max=100"; // the max number to generate

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older try
{
// try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest();
}
catch(e)
{

// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
"MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// call server asynchronously function process()
{
// only continue if xmlHttp isn't void
if (xmlHttp)
{
// try to connect to the server
try
{
// ask for permission to call remote server, for Mozilla-based browsers
try
{
// this generates an error (that we ignore) if the browser is not
// Mozilla

netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead')
;
}
catch(e) {} // ignore error
// initiate server access
xmlHttp.open("GET", serverAddress + "?" + serverParams, true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}
// display the error in case of failure
catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleRequestStateChange()
{
// when readyState is 4, we are ready to read the server response if (xmlHttp.readyState == 4)
{

// continue only if HTTP status is "OK" if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server handleServerResponse();
}
catch(e)
{
// display error message
alert("Error reading the response: " + e.toString());
}
}
else
{
// display status message
alert("There was a problem retrieving the data:\n" +
xmlHttp.statusText);
}
}
}

// handles the response received from the server function handleServerResponse()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// obtain a reference to the <div> element on the page
myDiv = document.getElementById('myDivElement');
// display the HTML output
myDiv.innerHTML = "New random number retrieved from server: "
+ response + "<br/>";
}

4. Load http://localhost/ajax/foundations/ping/ping.html. If you are using Internet Explorer with the default options, you will be asked whether you will allow the script to connect to a remote server as shown in Figure 3.8. If you are using Firefox or Opera with the default options, you will get security errors like the ones shown in Figure 3.9 and Figure 3.10, respectively.

Figure 3.8: Internet Explorer Asking for Permission

Figure 3.9: Firefox Denying Access

Figure 3.10: Opera Denying Access

5. Now try to load the very same HTML file but directly from the file system. The path to the file should be like file:///C:/Apache2/htdocs/ajax/foundations/ ping/ping.html. With the default options, Internet Explorer will run with no problems, because the page is located in a trusted zone. Firefox will ask for a confirmation as shown in Figure 3.11. Opera will display the very same error message that you saw in Figure 3.10.

Figure 3.11: Firefox Asking for Permission

What Just Happened?
Opera is indeed the safest browser in the world. You have no way of convincing Opera 8.5 to allow the JavaScript code to access a different server than the one it was loaded from.

Internet Explorer behaves as instructed by the zones settings. By default, it will make your life easy enough, by giving maximum trust to local files, and by asking for confirmation when scripts loaded from the Internet try to do potentially dangerous actions.

Firefox has to be asked politely if you want to have things happen. The problem is that by default it won't even listen for your polite request unless the script is signed, or loaded from a local file:// location. However, requesting your visitor to change browser settings isn't a real option in most scenarios.

You can make Firefox listen to all requests, even those coming from unsigned scripts, by typing about:config in the address bar, and changing the value of signed.applets.codebase_principal_support to true.

The following is the code that asks Firefox for permission to access a remote server:

// ask for permission to call remote server, for Mozilla-based browsers try
{
// this generates an error (that we ignore) if the browser is not
// Mozilla

netscape.security.PrivilegeManager.enablePrivilege('UniversalBrowserRead');
}
catch(e) {}
// ignore error

Any errors in this code are ignored using the try/catch construct because the code is
Mozilla-specific, and it will generate an exception on the other browsers.

Using a Proxy Server Script
It is quite clear that unless you are building a solution where you can control the environment,
such as ensuring that your users use Internet Explorer or Firefox (in which case you would need to sign your scripts or configure the browsers manually to be more permissive), accessing remote servers from your JavaScript code is not an option.

The very good news is that the workaround is simple; instead of having the JavaScript access the remote server directly you can have a PHP script on your server that will access the remote server on behalf of the client. This technique is described in the following figure:

Figure 3.12: Using a Proxy PHP Script to Access a Remote Server

To read data from a remote server with PHP we will use the file_get_contents function, whose documentation can be found at http://www.php.net/manual/en/function.file-get- contents.php.

A popular (and more powerful) alternative to using file_get_contents is a library called Client URL Library (CURL). You can find more details about CURL from http://curl.haxx.se, http://www.php.net/curl and http://www.zend.com/ zend/tut/tutorial-thome3.php. For basic needs though, file_get_contents gets the job done nicely and easily.

Let's try this out with some code. The functionality we want to implement is the same as in the previous exercise (get a random number and display it), but this time it will work with all browsers.

Time for Action—Using a Proxy Server Script to Access Remote Servers
1. In the foundations folder, create a subfolder named proxyping.
2. In the proxyping folder, create proxyping.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Accessing Remote Server through Proxy PHP Script</title>
<script type="text/javascript" src="proxyping.js"></script>
</head>
<body onload="process()">
Server, tell me a random number!<br/>
<div id="myDivElement" />
</body>
</html>

3. In the same folder create proxyping.js. Note that this file is similar to ping.js, and the new bits are highlighted. (We removed the bits that handle Mozilla security from process(), changed the server address in the header, removed the num parameter because in this scenario we'll only request one number at a time, and added an error- handling measure.)
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// holds the remote server address and parameters var serverAddress = "proxyping.php";
var serverParams = "&min=1" + // the min number to generate
"&max=100"; // the max number to generate

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older
try
{
// try to create XMLHttpRequest object
xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0",
"MSXML2.XMLHTTP.5.0",

"MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// call server asynchronously function process()
{
// only continue if xmlHttp isn't void
if (xmlHttp)
{
// try to connect to the server
try
{
// initiate server access
xmlHttp.open("GET", serverAddress + "?" + serverParams, true); xmlHttp.onreadystatechange = handleRequestStateChange; xmlHttp.send(null);
}
// display the error in case of failure catch (e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleRequestStateChange()
{
// when readyState is 4, we are ready to read the server response if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK" if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server
handleServerResponse();
}
catch(e)
{
// display error message
alert("Error reading the response: " + e.toString());
}
}
else

{
// display status message
alert("There was a problem retrieving the data:\n" +
xmlHttp.statusText);
}
}
}

// handles the response received from the server function handleServerResponse()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// if the response is longer than 3 characters, or if it is void, we
// assume we just received a server-side error report if(response.length > 3 || response.length == 0)
throw(response.length == 0 ? "Server error" : response);
// obtain a reference to the <div> element on the page myDiv = document.getElementById("myDivElement");
// display the HTML output
myDiv.innerHTML = "Server says: " + response + "<br/>";
}

4. Build the hero proxy PHP script, proxyping.php:
<?php
// load the error handling module require_once('error_handler.php');
// make sure the user's browser doesn't cache the result header('Expires: Wed, 23 Dec 1980 00:30:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
// retrieve the parameters
$num = 1; // this is hardcoded on the server
$min = $_GET['min'];
$max = $_GET['max'];
// holds the remote server address and parameters
$serverAddress = 'http://www.random.org/cgi-bin/randnum';
$serverParams = 'num=' . $num . // how many random numbers to generate
'&min=' . $min . // the min number to generate
'&max=' . $max; // the max number to generate
// retrieve the random number from foreign server
$randomNumber = file_get_contents($serverAddress . '?' . $serverParams);
// output the random number echo $randomNumber;
?>

5. Finally, add the error-handler function. Yes, it's a bit more to type, but it does good things to your solution (you can copy and paste it from other examples, because it
is not going to change). Create a new file named error_handler.php, and write this code:
<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);
// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .

'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts
exit;
}
?>

6. Load http://localhost/ajax/foundations/proxyping/proxyping.html with your favorite web browser (yes, even with Opera), and admire the random number you get.

Figure 3.13: Using a Proxy PHP Script to Access the Remote Server

What Just Happened?
The JavaScript code is allowed to access the server it was loaded from. We placed a script on the server, called proxyping.php, which accesses the random number generator server on the behalf of the client.

In order for the client to still have complete control over what kind of number to receive, we pass the min and max parameters to the PHP script, and the PHP script passes them in its turn to the random number generator server. We don't pass the num parameter from the client because now we don't want to give the client the option to ask for more than one number at a time. In this example, if the response is larger than 3 characters, we assume we received a server error report:

// handles the response received from the server function handleServerResponse()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// if the response is longer than 3 characters, or if it is void, we assume
// we just received a server-side error report if(response.length > 3 || response.length == 0)
throw(response.length == 0 ? "Server error" : response);

Errors can happen on the client side, or on the server side. We made efforts to have the client protected by implementing a try/catch mechanism in key portions of the code. On the other hand, when an error happens on the server, that error doesn't propagate to the client as a client error. Instead, on the client we must manually analyze the input received from the server, and if it doesn't look like what we expected, we generate an error manually using throw.

If the display_errors setting in php.ini is set to Off, when a PHP parse or fatal error happens, the error is logged only to the Apache error log file (Apache/logs/error.log), and the script's output will be void. So if we receive a void response, we also assume that something bad happened on the server, and we build a generic error message on the client.

For example, if you try to load the page when no internet connection is available (so the remote server isn't reachable), then it should result in the following error being displayed (the error message will look differently if display_errors is set to Off in php.ini):

Figure 3.14: An Error Message When No Internet Connection is Available

The code in proxyping.php simply uses the parameters received though GET to access the random number generator server. One interesting detail to note in this script is the way we set the page expiration headers. Setting page expiration is important because the server is always called using the same URL and query string, and the client browser may decide to cache the result—and we don't want that, because the results wouldn't be exactly random any more.

<?php
// load the error handling module
require_once('error_handler.php');
// make sure the user's browser doesn't cache the result header('Expires: Wed, 23 Dec 1980 00:30:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache');

You can find an excellent article on page caching and PHP at http://www.sitepoint.com/ article/php-anthology-2-5-caching. The remainder of proxyping.php simply uses the file_get_contents function to retrieve a response from the random number generator service, and output it for the client.

// retrieve the parameters
$num = 1; // this is hardcoded on the server
$min = $_GET['min'];
$max = $_GET['max'];
// holds the remote server address and parameters
$serverAddress = 'http://www.random.org/cgi-bin/randnum';
$serverParams = 'num=' . $num . // how many random numbers to generate
'&min=' . $min . // the min number to generate
'&max=' . $max; // the max number to generate
// retrieve the random number from foreign server
$randomNumber = file_get_contents($serverAddress . '?' . $serverParams);
// output the random number echo $randomNumber;
?>

A Framework for Making Repetitive Asynchronous
Requests
Quite frequently when building AJAX applications, you will need your client script to retrieve
data from the server at regular intervals. There are numerous example scenarios, and you will meet many in this book, and perhaps many more in your real-world projects.

JavaScript offers four functions that can help achieving repetitive (or scheduled) functionality:
setTimeout, setInterval, clearTimeout, and clearInterval, which can be used like this:

// using setTimeout and clearTimeout
timerId = window.setTimeout("function()", interval_in_milliseconds);
window.clearTimeout(timeId);
// using setInterval and clearInterval
timerId = window.setInterval("function()", interval_in_milliseconds);
window.clearInterval(timeId);

setTimeout causes the function to be executed once, after the specified time period. setInterval executes the function repeatedly, at the mentioned interval, until clearInterval is used. In most AJAX scenarios we prefer using setTimeout because it offers more flexibility in controlling when the server is accessed.

For a quick demonstration, we will extend the client that reads random numbers by making the following improvements:

• When making a server request, we wait until the response containing the random number is received, and then we use setTimeout to restart the sequence (to make a new server request) after one second. This way, the interval between two requests is one second plus the time it takes to retrieve the random number. If you want to make the requests at exact periods, you must use setInterval, but in that case you need to check that the XMLHttpRequest object isn't busy waiting to complete the previous request (which can happen if the network is slow, or the server busy).
• In this new example, we will also check for the server's availability from time to time.
The random number generator service has a buffer of random numbers, which is used to serve the requests, and anyone can check the buffer's level at http://www.random.org/ cgi-bin/checkbuf. Our program will check this page every 10 requests, and will request new random numbers only if the buffer level is at least 50%.

The web application will look like Figure 3.15:

Figure 3.15: Making Repetitive Asynchronous Requests

This repetitive task must start somewhere. In our application, everything starts with process(). There, we decide what kind of server request to make; we can either ask for a new random
number, or we can check for the buffer level of the random number generator server. We check for the buffer level every 10 requests, and by default we don't ask for new random numbers unless the buffer is higher than 50%. The process is described in the flowchart given opposite:

Figure 3.16: Flowchart Describing the Process of Retrieving Random Numbers

With the default code, setTimeout is only called to restart the process after successful HTTP requests; there is no setTimeout in the catch blocks. (Depending on your particular solution, you may want to try calling the server again after a while even if an error happens.)

Time for Action—Implementing Repetitive Tasks
1. In the foundations folder, create a new folder named smartproxyping.
2. In the smartproxyping folder, create a file named smartproxyping.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Making Repetitive Asynchronous Requests</title>
<script type="text/javascript" src="smartproxyping.js"></script>
</head>
<body onload="process()">

Server, gimme some random numbers!<br/>
<div id="myDivElement" />
</body>
</html>

3. In the same folder, create smartproxyping.js:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// holds the remote server address and parameters
var serverAddress = "smartproxyping.php";
var getNumberParams = "action=GetNumber" + // get a new random number
"&min=1" + // the min number to generate
"&max=100"; // the max number to generate var checkAvailabilityParams = "action=CheckAvailability";
// variables used to check for server availability
var requestsCounter = 0; // counts how many numbers have been retrieved var checkInterval = 10; // counts interval for checking server availability
var updateInterval = 1; // how many seconds to wait to get a new number var updateIntervalIfServerBusy = 10; // seconds to wait when server busy var minServerBufferLevel = 50; // what buffer level is considered acceptable

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older try
{
// try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// call server asynchronously function process()
{

// only continue if xmlHttp isn't void if (xmlHttp)
{
// try to connect to the server try
{
// if just starting, or if we hit the specified number of requests,
// check for server availability, otherwise ask for a new random number
if (requestsCounter % checkInterval == 0)
{
// check if server is available
xmlHttp.open("GET", serverAddress + "?" +
checkAvailabilityParams, true);
xmlHttp.onreadystatechange = handleCheckingAvailability;
xmlHttp.send(null);
}
else
{
// get new random number
xmlHttp.open("GET", serverAddress + "?" + getNumberParams, true);
xmlHttp.onreadystatechange = handleGettingNumber;
xmlHttp.send(null);
}
}
catch(e)
{
alert("Can't connect to server:\n" + e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleCheckingAvailability()
{
// when readyState is 4, we are ready to read the server response
if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK"
if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server checkAvailability();
}
catch(e)
{
// display error message
alert("Error reading server availability:\n" + e.toString());
}
}
else
{
// display status message
alert("Error reading server availability:\n" + xmlHttp.statusText);
}
}
}

// handles the response received from the server function checkAvailability()
{
// retrieve the server's response
var response = xmlHttp.responseText;

// if the response is long enough, or if it is void, we assume we just
// received a server-side error report
if(response.length > 5 || response.length == 0)
throw(response.length == 0 ? "Server error" : response);
// obtain a reference to the <div> element on the page
myDiv = document.getElementById("myDivElement");
// display the HTML output
if (response >= minServerBufferLevel)
{
// display new message to user
myDiv.innerHTML += "Server buffer level is at " + response + "%, "
+ "starting to retrieve new numbers. <br/>";
// increases counter to start retrieving new numbers requestsCounter++;
// reinitiate sequence
setTimeout("process();", updateInterval * 1000);
}
else
{
// display new message to user
myDiv.innerHTML += "Server buffer is too low (" + response + "%), "
+ "will check again in " + updateIntervalIfServerBusy
+ " seconds. <br/>";
// reinitiate sequence
setTimeout("process();", updateIntervalIfServerBusy * 1000);
}

}

// function called when the state of the HTTP request changes function handleGettingNumber()
{
// when readyState is 4, we are ready to read the server response if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK" if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server
getNumber();
}
catch(e)
{
// display error message
alert("Error receiving new number:\n" + e.toString());
}
}
else
{
// display status message
alert("Error receiving new number:\n" + xmlHttp.statusText);
}
}
}

// handles the response received from the server function getNumber()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// if the response is long enough, or if it is void, we assume we just
// received a server-side error report if(response.length > 5 || response.length == 0)

throw(response.length == 0 ? "Server error" : response);
// obtain a reference to the <div> element on the page
myDiv = document.getElementById("myDivElement");
// display the HTML output
myDiv.innerHTML += "New random number retrieved from server: "
+ response + "<br/>";
// increase requests count requestsCounter++;
// reinitiate sequences
setTimeout("process();", updateInterval * 1000);
}

4. In the same folder, create smartproxyping.php:
<?php
// load the error handling module
require_once('error_handler.php');
// make sure the user's browser doesn't cache the result header('Expires: Wed, 23 Dec 1980 00:30:00 GMT'); // time in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache');
// retrieve the action parameter
$action = $_GET['action'];
// check availability or get new random number?
if ($action == 'GetNumber')
{
$num = 1; // value is hardcoded because client can't deal with more numbers
$min = $_GET['min'];
$max = $_GET['max'];
// holds the remote server address and parameters
$serverAddress = 'http://www.random.org/cgi-bin/randnum';
$serverParams = 'num=' . $num . // how many random numbers to generate
'&min=' . $min . // the min number to generate
'&max=' . $max; // the max number to generate
// retrieve the random number from foreign server
$randomNumber = file_get_contents($serverAddress . '?' . $serverParams);
// output the random number echo $randomNumber;
}
elseif ($action == 'CheckAvailability')
{
// address of page that returns buffer level
$serverAddress = 'http://www.random.org/cgi-bin/checkbuf';
// received buffer level is in form 'x%'
$bufferPercent = file_get_contents($serverAddress);
// extract the number
$buffer = substr($bufferPercent, 0, strlen($bufferPercent) - 2);
// echo the number
echo $buffer;
}
else
{
echo 'Error talking to the server.';
}
?>

5. In the same folder, create the error_handler.php file, which should be identical to its version from the previous exercises:
<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);
// error handler function

function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts
exit;
}
?>

6. Load http://localhost/ajax/foundations/smartproxyping/
smartproxyping.html. The output should look like the one in Figure 3.15.

What Just Happened?
Our client, in this example, knows how to check from time to time if the server is available. The random number generator service provides the page http://www.random.org/cgi-bin/checkbuf
—which you can use to check its buffer level.

The JavaScript code in smartproxyping.js starts by defining a number of global variables that you use to control the program's behavior:

// holds the remote server address and parameters var serverAddress = "smartproxyping.php";
var getNumberParams = "action=GetNumber" + // get a new random number "&min=1" + // the min number to generate "&max=100"; // the max number to generate
var checkAvailabilityParams = "action=CheckAvailability";

// variables used to check for server availability
var requestsCounter = 0; // counts how many numbers have been retrieved
var checkInterval = 10; // counts interval for checking server availability var updateInterval = 1; // how many seconds to wait to get a new number
var updateIntervalIfServerBusy = 10; // seconds to wait when server busy
var minServerBufferLevel = 50; // what buffer level is considered acceptable

These variables contain the data required to make server requests. getNumberParams contains the query string parameters needed to request a new random number, and checkAvailabilityParams contains the parameters used to check the server's buffer level. The other variables are used to control the intervals for making the asynchronous requests.

A novelty in this exercise compared to the previous ones is that you have two functions that
handle server responses—handleCheckingAvailability and handleGettingNumber. The roots of this happen to be in the process() function, which assigns one of these callback functions depending on the server action it requests.

In this program, process() is not called only once as in other exercises; instead, it is called multiple times, and each time it must decide what action to make—should it ask for a new random number, or should it check the server's buffer level? The requestsCounter variable, which keeps
a track of how many times we have retrieved a new random number since the last buffer check, helps us make a decision:

function process()
{
// ...
if (requestsCounter % checkInterval == 0)
{
// check if server is available xmlHttp.open("GET", serverAddress + "?" +
checkAvailabilityParams, true);
xmlHttp.onreadystatechange = handleCheckingAvailability;
xmlHttp.send(null);
}
else
{
// get new random number
xmlHttp.open("GET", serverAddress + "?" + getNumberParams, true); xmlHttp.onreadystatechange = handleGettingNumber; xmlHttp.send(null);
}
// ...
}

The handleCheckingAvailability and handleGettingNumber functions are similar; they both are specialized versions of the handleRequestStateChange function you know from the previous exercises. Their role is to wait until the response has successfully been received from the server, and call a helper function (checkAvailability and getNumber) to deal with the response as soon as the response is in.

Notice the action query string parameter, which is used to tell the PHP script what kind of remote server request to make. On the server side, in smartproxyping.php, after loading the error-handling module, we read that action parameter and decide what to do depending on its value:

<?php
// load the error handling module require_once('error_handler.php');
// retrieve the action parameter
$action = $_GET['action'];
// check availability or get new random number?
if ($action == 'GetNumber')
{
// ...

If the action is GetNumber then we use the file_get_contents PHP function to read a new random number from the remote server:

if ($action == 'GetNumber')
{
$num = 1; // value is hardcoded because client can't deal with more numbers
$min = $_GET['min'];
$max = $_GET['max'];
// holds the remote server address and parameters
$serverAddress = 'http://www.random.org/cgi-bin/randnum';
$serverParams = 'num=' . $num . // how many random numbers to generate
'&min=' . $min . // the min number to generate
'&max=' . $max; // the max number to generate
// retrieve the random number from foreign server
$randomNumber = file_get_contents($serverAddress . '?' . $serverParams);
// output the random number echo $randomNumber;
}

If the action is CheckAvailability we call

elseif ($action == 'CheckAvailability')
{
// address of page that returns buffer level
$serverAddress = 'http://www.random.org/cgi-bin/checkbuf';
// received buffer level is in form 'x%'
$bufferPercent = file_get_contents($serverAddress);
// extract the number
$buffer = substr($bufferPercent, 0, strlen($bufferPercent) - 2);
// echo the number echo $buffer;
}

Note that the file_get_contents calls are not asynchronous, and they don't need to be. The PHP script isn't in direct connection with the user, and it can take as long as needed to complete. On the client side, the checkAvailability and getNumber functions receive these responses we are generating from the PHP script. The functions start by reading the response, and checking its size:

// handles the response received from the server function getNumber()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// if the response is long enough, or if it is void, we assume we just
// received a server-side error report if(response.length > 5 || response.length == 0)
throw(response.length == 0 ? "Server error" : response);

This is a method to check whether the PHP script executed successfully. Deciding whether the execution was successful depending on the size of the response is quite a primitive, but yet an efficient, method. The fact that PHP throws those fatal errors that can't be caught and dealt with makes it hard to implement a generic, powerful error- handling mechanism.

Apart from detecting the error, in a commercial implementation you will also need to think very seriously what to do with it—and the options are endless, depending on your circumstances. Keep in mind that users don't care about the technical details of the error. In our scenario, for example, we could simply output a message such as "The server is temporarily unavailable, please try later."

However, if you want to output the exact error message, consider that your custom- made errors use the \n new line character, while PHP's fatal errors output HTML formatted message. If you intend to display that message in a JavaScript box, you need to format it somehow.

After updating the client display, we reinitiate the sequence by using setTimeout:

// reinitiate sequences
setTimeout('process();', updateInterval * 1000);
}

Working with MySQL
A back-end data store is necessary when you implement any kind of application that is expected to generate some useful dynamic output. The most common ways to store the application's data are
in Relational Database Management Systems (RDBMS), which are very powerful tools that can store and manage our data.

Much like the other ingredients, the database is not a part of AJAX, but it's not likely that you'll be able to build real web applications without a database to support them. In this book, we'll present simple applications that don't have impressive data needs, but still require a database nevertheless. For the examples in this book we chose MySQL, which is a very popular database among PHP developers. However, because the database functionality is very generic, you can port it to other database systems with very little effort.

To build an application that uses databases you need to know the basics of:

1. Creating database tables that can hold your data
2. Writing SQL queries to manipulate that data
3. Connecting to your MySQL database using PHP code
4. Sending SQL queries to the database, and retrieving the results

Once again, we'll only be able to cover the very basics of working with PHP and MySQL databases here. The PHP and MySQL online free manuals are quite well written, so you may find them useful along the way.

Creating Database Tables
To create a data table you need to know the basic concepts of the structure of a relational database. A data table is made up of columns (fields), and rows (records). When creating a data table you need to define its fields, which can have various properties. Here we will discuss:

• Primary Keys
• Data Types
• NULL and NOT NULL columns
• Default column values
• auto_increment columns
• Indexes

The primary key is a special column (or set of columns) in a table that makes each row uniquely identifiable. The primary key column doesn't allow repeating values, so every value will be unique. When the primary key is formed of more than one column, then the set of columns (and not each column separately) must be unique. Technically, PRIMARY KEY is a constraint (a rule) that you apply to a column, but for convenience, when saying "primary key", we usually refer to the column that has the PRIMARY KEY constraint. When creating a PRIMARY KEY constraint, a unique index is also created on that column, significantly improving searching performance.

Each column has a data type, which describes its size and behavior. There are three important categories of data types (numerical types, character and string types, and date and time types), and each category contains many data types. For complete details on this subject refer to the official MySQL 5 documentation at http://dev.mysql.com/doc/refman/5.0/en/data-types.html.

When creating a new data table you must decide which values are mandatory, and mark them with the NOT NULL property, which says the column isn't allowed to store NULL values. The
definition of NULL is undefined. When reading the contents of the table you see NULL, it means a value has not been specified for that field. Note that an empty string, or a string containing spaces, or a value of "0" (for numerical columns) are real (non-NULL) values. The primary key field can't allow NULLs.

Sometimes instead of (or complementary to) disallowing NULLs for a certain field, you may want to specify a default value. In that case, when a new record is created, if a value isn't specified for that field, the default value will be used. For the default value you can also specify a function that will be executed to retrieve the value when needed.

A different way of letting the system generate values for you is by using auto_increment columns. This is an option you will often use for primary key columns, which represent IDs that you prefer to be auto-generated for you. You can set auto_increment only for numerical columns, and the newly generated values will be automatically incremented so no value will be generated twice.

Indexes are database objects used to improve the performance of database operations. An index is a structure that greatly improves searches on the field (or fields) it is set on, but it slows down the update and insert operations (because the index must be updated as well on these operations). A
well-chosen combination of indexes can make a huge difference in the speed of your application. In the examples in this book, we will rely on the indexes that we build on the primary key columns.

You can create data tables using SQL code, or using a visual interface. Here's an example of a
SQL command that creates a simple data table:

CREATE TABLE users
(
user_id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_name VARCHAR(32) NOT NULL,
PRIMARY KEY (user_id)
);

In case you don't like how you created the table, you have the option to alter it using ALTER TABLE, or to drop (delete) it altogether using DROP TABLE. You can use TRUNCATE TABLE to rapidly drop
and recreate the table (it has the same effect as deleting all the records, but it's much faster and also clears the auto-increment index).

For each exercise, we will give you the SQL code that builds the necessary data tables. You can execute this code by using a program such as phpMyAdmin (Appendix A describes the installation procedure). To execute SQL code using phpMyAdmin, you need to connect to a
database by selecting its name in the Database list, and clicking the SQL tab on the main panel, as shown in Figure 3.17.

Figure 3.17: Executing SQL Code Using phpMyAdmin

phpMyAdmin also gives you the possibility to create the tables visually, using forms as shown in
Figure 3.18.

Figure 3.18: Creating a New Table Using the phpMyAdmin Designer

If you were wondering about the Table type option, read on. MySQL is different than other database products in that it ships with several database engines, the two most popular being MyISAM and InnoDB. What's interesting is that you can have tables of different types in a single database, and you can specify the type for each table when creating it (otherwise, the default will be used, which on most configurations is MyISAM). Each engine has strengths and weaknesses, but probably the most powerful one is InnoDB, which fully supports the ACID (Atomicity, Consistency, Isolation, and Durability) properties of transactions, row-level locking, foreign keys and referential integrity, and other features. MyISAM's significant strength compared to the other engines is the included support for full-text searching, and (arguably) speed.

Manipulating Data
You can manipulate your data using SQL's DML (Data Manipulation Language) commands, SELECT, INSERT, UPDATE, and DELETE, used to retrieve, add, modify, and delete records from data tables. These commands are very powerful, and flexible. Their basic syntax is:

SELECT <column list> FROM <table name(s)>
[WHERE <restrictive condition(s)>]

INSERT INTO <table name> [(column list)] VALUES (column values)

UPDATE <table name>
SET <column name> = <new value> [, <column name> = <new value> ... ] [WHERE <restrictive condition>]

DELETE FROM <table name>
[WHERE <restrictive condition>]

A few basic things to keep in mind:

• The SQL code can be written in one or more lines, however you feel it looks nicer.
• If you want to execute several SQL commands at once, you must separate them using the semicolon (;).
• The values written between square brackets in the syntax are optional. (Be careful with the DELETE statement though; if you don't specify a restrictive condition, all elements will be deleted.)
• With SELECT, you can specify *, instead of the column list, which includes all the existing table columns.
• SQL is not case sensitive, but we will try to write the SQL statements in uppercase, and the table and field names in lowercase. Consistency is always good.

You can test how these commands work by practicing on the users table that was described earlier. Feel free to open a SQL tab in phpMyAdmin and execute commands such as:

INSERT INTO users (user_name) VALUES ('john'); INSERT INTO users (user_name) VALUES ('sam'); INSERT INTO users (user_name) VALUES ('ajax');

SELECT user_id, user_name FROM users;

UPDATE users SET user_name='cristian' WHERE user_id=1; SELECT user_id, user_name FROM users;
DELETE FROM users WHERE user_id=3; SELECT * FROM users WHERE user_id>1;
During the course of this book, you will meet much more complicated query examples, which will
be explained as necessary. Please remember that SQL is a big subject, so you will likely need additional resources if you haven't written much SQL code so far.

Connecting to Your Database and Executing Queries
In our examples, the code that connects to the database will be written in PHP. As Figure 3.19 shows, the database will never be accessed directly by the client, but only by the business logic written in the PHP code on the server.

Figure 3.19: User Connecting to MySQL through Layers of Functionality

To get to the necessary data, your PHP code will need to authenticate to the database.

Database security—as with any other kind of security system—involves two important concepts: authentication and authorization. Authentication is the process in which the user is uniquely identified using some sort of login mechanism (usually by entering a username and password). Authorization refers to the resources that can be accessed (and actions that can be performed) by the authenticated user.

If you configured MySQL security as shown in Appendix A, you will connect to your local MySQL server, to the database called ajax, with a user called ajaxuser, with the password practical. These details will be kept in a configuration file called config.php, which can be easily updated when necessary. The config.php script will look like this:

<?
// defines database connection data define('DB_HOST', 'localhost');
define('DB_USER', 'ajaxuser'); define('DB_PASSWORD', 'practical'); define('DB_DATABASE', 'ajax');
?>

This data will be used when performing database operations. Any database operation consists of three mandatory steps:

1. Opening the database connection
2. Executing the SQL queries and reading the results
3. Closing the database connection

It's a good practice to open the database connection as late as possible, and close it as soon as possible, because open database connections consume server resources. The following code snippet shows a simple PHP script that opens a connection, reads some data from the database, and closes the connection:

// connect to the database
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
// what SQL query you want executed?
$query = 'SELECT user_id, user_name FROM users';
// execute the query
$result = $mysqli->query($query);
// do something with the results...
// ...
// close the input stream
$result->close();
// close the database connection
$mysqli->close();

Note that we use the mysqli library to access MySQL. This is a newer and improved version of the mysql library, which provides both object-oriented and procedural interfaces to MySQL, and can access more advanced features of MySQL. If you have older versions of MySQL or PHP that don't support mysqli, use mysql instead.

The exercise that follows doesn't contain AJAX-specific functionality; it is just a simple example of accessing a MySQL database from PHP code.

Time for Action—Working with PHP and MySQL
1. Connect to the ajax database, and create a table named users with the following code:
CREATE TABLE users
(
user_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL, PRIMARY KEY (user_id)
);

2. Execute the following INSERT commands to populate your users table with some sample data:
INSERT INTO users (user_name) VALUES ('bogdan'); INSERT INTO users (user_name) VALUES ('filip'); INSERT INTO users (user_name) VALUES ('mihai'); INSERT INTO users (user_name) VALUES ('emilian'); INSERT INTO users (user_name) VALUES ('paula'); INSERT INTO users (user_name) VALUES ('cristian');

Because user_id is an auto_increment column, its values will be generated by the database.

3. In your foundations folder, create a new folder named mysql.
4. In the mysql folder, create a file named config.php, and add the database configuration code to it (change these values to match your configuration):
<?php
// defines database connection data define('DB_HOST', 'localhost');
define('DB_USER', 'ajaxuser'); define('DB_PASSWORD', 'practical'); define('DB_DATABASE', 'ajax');
?>

5. Now add the standard error-handling file, error_handler.php. Feel free to copy this file from the previous exercises:
<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);
// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated
if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts exit;
}
?>

6. Create a new file named index.php, and add this code to it:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Working with PHP and MySQL</title>
</head>
<body>

<?php
// load configuration file require_once('error_handler.php');
require_once('config.php');
// connect to the database
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
// the SQL query to execute
$query = 'SELECT user_id, user_name FROM users';
// execute the query
$result = $mysqli->query($query);
// loop through the results
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
// extract user id and name
$user_id = $row['user_id'];
$user_name = $row['user_name'];
// do something with the data (here we output it)
echo 'Name of user #' . $user_id . ' is ' . $user_name . '<br/>';
}
// close the input stream

$result->close();
// close the database connection
$mysqli->close();
?>

</body>
</html>

7. Test your script by loading
http://localhost/ajax/foundations/mysql/index.php with a web browser.

Figure 3.20: These User Names are Read from the Database

What Just Happened?
First of all, note that there is no AJAX going on here; the example is demonstrating plain PHP data access functionality. All the interesting things happen in index.php. The real functionality starts by loading the error handler, and the configuration scripts:

<?php
// load configuration file
require_once('error_handler.php');
require_once('config.php');

Then, just as mentioned, we create a new database connection:

// connect to the database
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);

Note that a database connection contains a reference to a specific database inside the database server, not to the database server itself. The database we connect to is ajax, which contains the users table that you created earlier. When performing queries on the created connection, you can count on having access to the users table:

// the SQL query to execute
$query = 'SELECT user_id, user_name FROM users';
// execute the query
$result = $mysqli->query($query);

After these commands execute, the $result variable contains a pointer to the results stream, which we read line by line using the fetch_array method. This method returns an array with the fields of the current result row, and moves the pointer to the next result row. We parse the results row by row in a while loop until reaching the end of the stream, and for each row we read its individual fields:

// loop through the results
while ($row = $result->fetch_array(MYSQLI_ASSOC))
{
// extract user id and name
$user_id = $row['user_id'];
$user_name = $row['user_name'];
// do something with the data (here we output it)
echo 'Name of user #' . $user_id . ' is ' . $user_name . '<br/>';
}

At the end, we close the open database objects so we don't consume any resources unnecessarily, and we don't keep any database locks that could hurt the activity of other queries running at the same time:

// close the input stream
$result->close();
// close the database connection
$mysqli->close();
?>

Wrapping Things Up and Laying Out the Structure
In this final section of the chapter, we are establishing the scheme of a basic code structure, which we will use in all the following case studies. Most of the basic building blocks have already been presented, except for separating the sever-side business logic in a separate class, which will be demonstrated in a new exercise.

So far, the server-side code was always built as a single PHP file. In order to achieve better flexibility and a more powerful design, we will split the server-side PHP functionality in two files:

• One script, called appname.php (where appname is the name of your application) will be the main access point for the client-side JavaScript code. It will deal with the
input parameters received through POST and GET, and will make decisions based on these parameters.
• The second script, called appname.class.php, will contain a helper class named Appname, which encapsulates the real functionality that needs to be processed. The methods of this class will be called by appname.php depending on the requested action.

To fully understand the code you need to know the basics of OOP, and how this works with PHP. We don't cover these aspects in this book, but here are a few major things to keep in mind:

• OOP is based on the notion of classes, which are the blueprints for objects. Classes are formed of class members, which include methods (functions inside a class), the constructor, the destructor, and class fields (other OOP languages include even more class member types). Class fields are just like variables, but they have a
class-wide scope.

• In classes, you can implement two special methods called the constructor and destructor. The constructor is called construct(), and is executed automatically when you create new instances of a class. The constructor is useful when you have code that initializes various class members, because you can rely on it always executing as soon as a new object of the class is created.
• The destructor is named destruct(), and is called automatically when the object is destroyed. Destructors are very useful for doing housekeeping work. In most examples, we will close the database connection in the destructor, ensuring that we don't leave any database connections open, consuming unnecessary resources.
• It is true that it may be a bit better for performance to create the database connection just before needing it, instead of the class constructor, and to close it right after using it, instead of the class destructor. However, we choose to use the constructor and destructor because we get cleaner code where we are less likely to cause errors by forgetting to close the connection, for example.

When referring to any class member, you must specify the object it is a part of. If you want
to access a local class member, you must use the special $this object, that refers to the current class instance.

The public interface of a class consists of its public members, which are accessible from the outside, and can be used by programs that create instances of the class. Class members can be public, private, or protected. Private members can be used only internally by the class, and protected members can be used by derived classes.

Separating the various layers of functionality of an application is important, because it allows you to build flexible and extensible applications that can be easily updated when necessary. In Cristian Darie and Mihai Bucica's PHP e-commerce books, you even learn how to use a templating engine called Smarty that allows you to further separate presentation logic from the HTML template, so that designers are not bothered with the programming part of the site.

When preparing the design of your code, keep in mind is that the power, flexibility, and scalability of the architecture is directly proportional to the time you invest in designing it and writing the foundation code. Reference to these issues is available for free download at http:// ajaxphp.packtpub.com/ajax/

For this final exercise, we will build a simple but complete AJAX application called friendly, that implements many of the practices and techniques shown so far. The application will have a standard structure, composed of these files:

• index.html is the file loaded initially by the user. It contains the JavaScript code that makes asynchronous requests to friendly.php.
• friendly.css is the file containing the CSS styles to be used in the application.
• friendly.js is the JavaScript file loaded together with index.html on the client side. It makes asynchronous requests to a PHP script called friendly.php to perform various functionality required to support the rich client interface.

• friendly.php is a PHP script residing on the same server as index.html, and it offers the server-side functionality requested asynchronously by the JavaScript code in index.html. Remember that it is important for these files to reside on the same server, because the JavaScript code, when executed by the client, may not be allowed to access other servers. In most cases, friendly.php will make use of the functionality of yet another PHP file, named friendly.class.php, to perform its duties.
• friendly.class.php is a PHP script that contains a class called Friendly, which contains the business logic and database operations to support the functionality of friendly.php.
• config.php will be used to store global configuration options for your application, such as database connection data, etc.
• error_handler.php contains the error-handling mechanism that changes the text of an error message into a human-readable format.

The Friendly application, at configurable intervals (by default, of 5 seconds), reads two random records from the users table that you have created at the MySQL exercise, and reads a random number from the random number generator service that you have also met earlier in this chapter. Using this data, the server composes a message like "User paula works with user emilian at project
#33", which is read by the client and displayed as shown in Figure 3.21.

Figure 3.21: Friendly Web Application

The application will display "Reading the new message from server…" while making the asynchronous request (you get to read this message because the server adds an artificial delay to simulate some more complex server-side functionality).

In the case of an error, the application can be configured to display a detailed error message
(useful when debugging), as shown in Figure 3.22, or a more user friendly error message as shown in Figure 3.23.

Figure 3.22: What Happens When you Lose the Database Password—A Detailed Error Page

Figure 3.23: A Friendlier Error Page

Now that you know what we are up to, it's time for action…

Time for Action—Building the Friendly Application
1. This exercise makes use of the users table that is created in the previous exercise. If you haven't already, please follow steps 1 and 2 of the Working with PHP and
MySQL exercise.
2. Create a new folder named friendly as a child of the foundations folder.
3. Create a new file named index.html with this code in it:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
<head>
<title>Practical AJAX: Friendly Web Application</title>

<link href="friendly.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="friendly.js"></script>
</head>
<body onload="process()">
<noscript>
<strong>
This example requires a JavaScript-enabled browser!<br/><br/>
</strong>
</noscript>
<div>
<span>Welcome to AJAX Friendly!</span>
<br/><br/>
<div>
Your news for today:
<div id="myDivElement" />
</div>
</div>
</body>
</html>

4. Add a new file named friendly.css:
body
{
font-family: Arial, Helvetica, sans-serif;
font-size: small;
background-color: #fffccc;
}

input
{
margin-bottom: 3px;
border: #000099 1px solid;
}

.title
{
font-size: x-large;
}

div.project
{
background-color: #99ccff;
padding: 5px;
border: #000099 1px solid;
}

div.news
{
background-color: #fffbb8;
padding: 2px;
border: 1px dashed;
}

5. Now add the JavaScript source file, friendly.js:
// holds an instance of XMLHttpRequest
var xmlHttp = createXmlHttpRequestObject();
// holds the remote server address and parameters
var serverAddress = "friendly.php?action=GetNews";
// variables that establish how often to access the server
var updateInterval = 5; // how many seconds to wait to get new message
var errorRetryInterval = 30; // seconds to wait after server error
// when set to true, display detailed error messages var debugMode = true;

// creates an XMLHttpRequest instance function createXmlHttpRequestObject()
{
// will store the reference to the XMLHttpRequest object var xmlHttp;
// this should work for all browsers except IE6 and older try
{
// try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest();
}
catch(e)
{
// assume IE6 or older
var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP");
// try every prog id until one works
for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++)
{
try
{
// try to create XMLHttpRequest object
xmlHttp = new ActiveXObject(XmlHttpVersions[i]);
}
catch (e) {}
}
}
// return the created object or display an error message
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}

// function that displays a new message on the page function display($message)
{
// obtain a reference to the <div> element on the page myDiv = document.getElementById("myDivElement");
// display message
myDiv.innerHTML = $message + "<br/>";
}

// function that displays an error message function displayError($message)
{
// display error message, with more technical details if debugMode is true display("Error retrieving the news message! Will retry in " +
errorRetryInterval + " seconds." +
(debugMode ? "<br/>" + $message : ""));
// restart sequence
setTimeout("process();", errorRetryInterval * 1000);
}

// call server asynchronously function process()
{
// only continue if xmlHttp isn't void
if (xmlHttp)
{

// try to connect to the server try
{
// remove this line if you don't like the 'Receiving...' message display("Receiving new message from server...")
// make asynchronous HTTP request to retrieve new message xmlHttp.open("GET", serverAddress, true); xmlHttp.onreadystatechange = handleGettingNews; xmlHttp.send(null);
}
catch(e)
{
displayError(e.toString());
}
}
}

// function called when the state of the HTTP request changes function handleGettingNews()
{
// when readyState is 4, we are ready to read the server response if (xmlHttp.readyState == 4)
{
// continue only if HTTP status is "OK" if (xmlHttp.status == 200)
{
try
{
// do something with the response from the server
getNews();
}
catch(e)
{
// display error message displayError(e.toString());
}
}
else
{
// display error message displayError(xmlHttp.statusText);
}
}
}

// handles the response received from the server function getNews()
{
// retrieve the server's response
var response = xmlHttp.responseText;
// server error?
if (response.indexOf("ERRNO") >= 0
|| response.indexOf("error") >= 0
|| response.length == 0)
throw(response.length == 0 ? "Server error." : response);
// display the message
display(response);
// restart sequence
setTimeout("process();", updateInterval * 1000);
}

6. It's time to write the server-side scripts now. Start by creating friendly.php:
<?php
// load the error handling module require_once('error_handler.php'); require_once('friendly.class.php');

// make sure the user's browser doesn't cache the result header('Expires: Wed, 23 Dec 1980 00:30:00 GMT'); // time in the past header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Cache-Control: no-cache, must-revalidate');
header('Pragma: no-cache');
// read the action parameter
$action = $_GET['action'];
// get news
if ($action == 'GetNews')
{
// create new instance of the Friendly class
$friendly = new Friendly();
// use Friendly functionality to retrieve the news message
$news = $friendly->getNews();
// echo the message to be read by the client echo $news;
}
else
{
echo 'Communication error: server doesn\'t understand command.';
}
?>

7. Create the friendly.class.php script with the following contents:
<?php
// load error handling sequence require_once ('error_handler.php');
// load configuration require_once ('config.php');

// class stores Friendly web application functionality class Friendly
{
// stores the database connection private $mMysqli;

// constructor opens database connection function __construct()
{
$this->mMysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE);
}

// generate news message public function getNews()
{
// this will store the news line
$news = 'No news for today.';
// SQL that selects two random users from the database.
$query = 'SELECT user_name FROM users ' .
'ORDER BY RAND() ' .
'LIMIT 2';
// execute the query
$result = $this->mMysqli->query($query);
// retrieve the user rows
$row1 = $result->fetch_array(MYSQLI_ASSOC);
$row2 = $result->fetch_array(MYSQLI_ASSOC);
// close the input stream
$result->close();
// generate the news
if (!$row1 || !$row2)
{
$news = 'The project needs more users!';
}
else
{

// create HTML-formatted news message
$name1 = '<b>' . $row1['user_name'] . '</b>';
$name2 = '<b>' . $row2['user_name'] . '</b>';
$randNum = $this->getRandomNumber();
$news = 'User ' . $name1 . ' works with user ' . $name2 .
' at project #' . $randNum . '.';
}
// output the news line
return $news;
}

// returns a random number between 1 and 100 private function getRandomNumber()
{
// delays execution for quarter of a second usleep(250000);
// holds the remote server address and parameters
$serverAddress = 'http://www.random.org/cgi-bin/randnum';
$serverParams = 'num=1&min=1&max=100';
// retrieve the random number from remote server
$randomNumber = file_get_contents($serverAddress . '?' .
$serverParams);
// output the random number
return trim($randomNumber);
}

// destructor closes database connection function __destruct()
{
$this->mMysqli->close();
}
}
?>

8. Add the configuration file, config.php:
<?php
// defines database connection data define('DB_HOST', 'localhost'); define('DB_USER', 'ajaxuser'); define('DB_PASSWORD', 'practical'); define('DB_DATABASE', 'ajax');
?>

9. Finally, add the error-handler script, error_handler.php:
<?php
// set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL);
// error handler function
function error_handler($errNo, $errStr, $errFile, $errLine)
{
// clear any output that has already been generated
if(ob_get_length()) ob_clean();
// output the error message
$error_message = 'ERRNO: ' . $errNo . chr(10) .
'TEXT: ' . $errStr . chr(10) .
'LOCATION: ' . $errFile .
', line ' . $errLine;
echo $error_message;
// prevent processing any more PHP scripts exit;
}
?>

10. Load http://localhost/ajax/foundations/friendly/.

What Just Happened?
Most of the principles implemented in the application were covered earlier in the book, so we will quickly analyze what's new here, starting from the client-side code. The novelty in index.html consists in using the <noscript> element to offer a minimal support for browsers that don't support JavaScript, or for ones whose JavaScript support has been disabled:

<body onload="process()">
<noscript>
<strong>
This example requires a JavaScript-enabled browser!<br/><br/>
</strong>
</noscript>

Browsers that have JavaScript enabled will ignore everything between <noscript> and
</noscript>, while the others will parse and display that HTML code.

The client-side JavaScript file, friendly.js has a few surprises of its own:
• We grouped common functionality that handles displaying user messages into the display and displayError functions. Both receive as parameter the message to be displayed, but displayError displays the message only if debugMode is true (this variable is defined at the beginning of the file).
• displayError is called in the catch blocks after an exception has been thrown somewhere, and it uses setTimeout to restart the sequence that makes server requests. You can set how much time the script should wait before attempting
a new server request when an error happens by modifying the value of the
errorRetryInterval variable.
• You can change how often the news message should be displayed by changing the
updateInterval variable.
• In getNews(), we have a simplistic mechanism that checks whether the text received from the server was a server-side error instead of the message we are waiting for. This mechanism verifies if the response contains "ERRNO" (which is generated by our server-side custom error handler), or "error" (which is generated automatically
by PHP in the case of fatal errors or parse errors), or if the response is empty (if the displayErrors option is set to Off in php.ini, no error text is generated). In any of these cases, we throw an error manually, which is then received by our
error-handling mechanism that informs the users that an error has happened.

At the server side, everything starts in friendly.php, which is called from the client. The most important part of friendly.php is the one where it creates a new instance of the Friendly class (defined in friendly.class.php), and calls its getNews method:

// read the action parameter
$action = $_GET['action'];
// get news
if ($action == 'GetNews')
{
// create new instance of the Friendly class
$friendly = new Friendly();
// use Friendly functionality to retrieve the news message
$news = $friendly->getNews();
// echo the message to be read by the client
echo $news;
}

On the server side, all the interesting things happen in friendly.class.php, which is called from friendly.php to do the interesting part of the work. In friendly.class.php you can find the Friendly class, which has the following four members:

• $mMysqli: A private field that stores an open database connection during the life of the object.
• construct(): The class constructor initializes $mMysqli by opening a database connection. Because the constructor is executed automatically when an instance of the class is created, you can safely assume to have the connection available in all methods of the class.
• destruct(): The class destructor closes the database connection. The destructor is executed automatically when the class instance is destroyed.
• getRandomNumber(): This is a private helper method that returns a random number.
Private methods can't be called from programs that create instances of the class, and are meant to provide internal functionality only. The code in getRandomNumber is familiar from the previous exercises, as it calls the external random.org server to
retrieve new random numbers. The usleep PHP function is used to artificially add a
quarter of a second delay, so that you can admire the "Receiving new message from server…" message on the client for a little longer.
• getNews(): This is a public method that an external program can access to get a new "news" message. The method gets two random user names from the database, uses the getRandomNumber method to retrieve a random number, and composes a message such as "User x works with user y at project #z". (Yes that's not very imaginative but we couldn't think of anything more interesting—sorry!) Note the
$this special object that is used to access $mMysqli and getRandomNumber(). Class members can only be accessed using an instance of the class and in PHP
$this refers to the current class instance.

Summary
Hopefully, you have enjoyed the little examples of this chapter, because many more will follow! This chapter walked you through the technologies that live at the server side of a typical AJAX application. We have done a few exercises that involved simple server functionality, and PHP did a wonderful job at delivering that functionality. You have also learned the basics of working with databases, and simple database operations with the first table created in this book.

In the following chapters, you'll meet even more interesting examples that use more advanced
code to implement their functionality. In Chapter 4, you'll build an AJAX-enabled form validation page, which is safe to work even if the client doesn't support JavaScript and AJAX.

0 comments: