- What is Ajax?
- Fundamental Ajax
- Adding Ajax Elements to Earlier Projects
- For More Information
Adding Ajax Elements to Earlier Projects
None of the projects in Part V of this book, “Building Practical PHP and MySQL Projects,” are Ajax-enabled out of the box. Each project consists of a series of form submissions and page reloads. Although the pages contain dynamic elements, none are of the streamlined user experience we expect in the era of Web 2.0.
However, including Ajax elements in these projects would have shifted the focus away from the basics of creating web applications with PHP and MySQL. In other words, you have to learn to walk before you can run. But now that you know how to run—or at least jog a little—you can begin to think about modifying elements of these applications to include Ajax, or you can begin to think about including Ajax elements in new applications you might create.
The thought process of the Ajax developer goes something like this: What are the distinct user actions, and what page events will invoke those actions? For example, do you want your users always to press a button element to submit a form and move on to the next step, or can simply changing the focus on a form element (text field, option button, check box) invoke an asynchronous request to the web server? After you decide the types of actions you want to include, you can begin writing the JavaScript functions that invoke PHP scripts that handle the request to and result from the server.
In the following sections you’ll add some Ajax elements to existing scripts created previously in this book.
Adding Ajax Elements to PHPbookmark
In Chapter 27, “Building User Authentication and Personalization,” you created an application called PHPbookmark. This application requires user registration and login before saving bookmarks and getting recommendations for bookmarks you might like that have been saved by other users.
Because this application is already created and consists of several tightly connected PHP scripts and libraries of functions, the first step is to think of how to add additional files into the mix—whether they are style sheets, JavaScript functions, or PHP to handle actions on the server side. The answer is simple: create a separate file for styles and a separate file for all JavaScript functions. Then add a snippet of code to the existing PHP scripts from Chapter 27 to include these external files, when necessary, and the invocation of the JavaScript functions themselves. Any additional PHP scripts you create will also be kept separate from the existing application files.
After determining how to manage your new files, it’s time to determine which user functions can get the Ajax treatment. Although the user registration and login portion of the application could be a prime candidate for becoming Ajax-enabled, in the interest of space we have selected the functionality surrounding adding and editing the bookmarks stored by the user.
You will also make changes to the existing application files. It’s a good idea to copy the files from Chapter 27 into a new directory for use with this chapter; any changes you make will then be uploaded into this new directory rather than a working version of PHPbookmark you might already have installed.
Creating Additional Files
As mentioned previously, you will add new files into the existing application structure. Although you will fill in these files as you go through the sections that follow, it is a good idea to get your bearings before you continue.
Assume you will have at least two new files: a style sheet and a library of JavaScript functions. Create these two files now; call them new_ss.css and new_ajax.js. The new style sheet (new_ss.css) can be empty, because we haven’t yet defined new styles, but the new_ajax.js file should contain the getXMLHTTPRequest() function you created earlier in the chapter to create a new instance of the XMLHTTPRequest object in all browsers. Although you will be adding to these files, you can upload them as is to your web server at this time.
The next step is to add a link to both these files in one of the existing display functions for the PHPbookmark application. Doing so will ensure that the styles from the style sheet are always available, as are the functions from the JavaScript library. If you recall from Chapter 27, the function that controls the output of the head element of the HTML (among other things) is called do_html_header(), and it resides in the output_fns.php file.
A new version of this function is shown in Listing 34.4.
Listing 34.4 Amended Version of do_html_header() Containing Links to the New Style Sheet and JavaScript Function Libraries
function do_html_header($title) { // print an HTML header ?> <html> <head> <title><?php echo $title;?></title> <style> body { font-family: Arial, Helvetica, sans-serif; font-size: 13px; } li, td { font-family: Arial, Helvetica, sans-serif; font-size: 13px; } hr { color: #3333cc; } a { color: #000000; } </style> <link rel="stylesheet" type="text/css" href="new_ss.css"/> <script src="new_ajax.js" type="text/javascript"></script> </head> <body> <img src="bookmark.gif" alt="PHPbookmark logo" border="0" align="left" valign="bottom" height="55" width="57" /> <h1>PHPbookmark</h1> <hr /> <?php if($title) { do_html_heading($title); } }
If you upload the new style sheet, the JavaScript functions library, the output_fns.php file, and open any page in the PHPbookmark system, the new files should be included without error. Next, you’ll actually put additional styles and scripts into these files and create some Ajax functionality.
Adding Bookmarks the Ajax Way
Currently, adding a bookmark occurs when a user enters the bookmark URL and presses the form submission button. The act of pressing the form submission button invokes another PHP script, which adds the bookmark, returns the user to the list of bookmarks, and shows that the new bookmark has been added. In other words, pages are reloaded.
The Ajax way is to present the form for adding a bookmark, but instead of the form submission button requiring more page loads, it invokes a JavaScript function in the background that calls a PHP script to add the item to the database and return the response to the user—all without leaving the page that has already been loaded. This new functionality first requires a change to the display_add_bm_form() function in output_fns.php.
Listing 34.5 shows the amended function. We have removed the form action, added an id value to the input field, and changed the attributes of the button element. We have also added a call to the getXMLHTTPRequest() JavaScript function.
Listing 34.5 Amended Version of display_add_bm_form()
function display_add_bm_form() { // display the form for people to enter a new bookmark in ?> <script type="text/javascript"> var myReq = getXMLHTTPRequest(); </script> <form> <table width="250" cellpadding="2" cellspacing="0" bgcolor="#cccccc"> <tr><td>New BM:</td> <td><input type="text" name="new_url" name="new_url" value="http://" size="30" maxlength="255"/></td></tr> <tr><td colspan="2" align="center"> <input type="button" value="Add bookmark" onClick=" javascript:addNewBookmark();"/></td></tr> </table> </form> <?php }
Take a closer look at the button element:
<input type="button" value="Add bookmark" onClick=" javascript:addNewBookmark();"/>
When the button is clicked, the onClick event handler invokes the addNewBookmark() JavaScript function. This function makes a request to the server, specifically to a PHP script that attempts to insert the record into the database. The code for this function is found in Listing 34.6.
Listing 34.6 The JavaScript Function addNewBookmark()
function addNewBookmark() { var url = "add_bms.php"; var params = "new_url=" + encodeURI(document.getElementById('new_url').value); myReq.open("POST", url, true); myReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); myReq.setRequestHeader("Content-length", params.length); myReq.setRequestHeader("Connection", "close"); myReq.onreadystatechange = addBMResponse; myReq.send(params); }
This function should look similar to the getServerTime() function used earlier in this chapter. The process is quite similar: create variables, send the data to a PHP script, and invoke a function to handle the response from the server.
The following line creates a name/value pair from the name of the form field and the value entered by the user:
var params = "new_url=" + encodeURI(document.getElementById('new_url').value);
The value of params is then sent to the back-end PHP script in the last line of the function:
myReq.send(params);
Before the values are sent, three request headers are also sent so that the server knows how to handle the data sent in the POST request:
myReq.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); myReq.setRequestHeader("Content-length", params.length); myReq.setRequestHeader("Connection", "close");
The next step in this process is to create the JavaScript function to handle the server response; we have called this addBMResponse:
myReq.onreadystatechange = addBMResponse;
Again, this code is similar to the theHTTPResponse function created earlier in the chapter. The code for addBMResponse is seen in Listing 34.7.
Listing 34.7 The JavaScript Function addBMResponse()
function addBMResponse() { if (myReq.readyState == 4) { if(myReq.status == 200) { result = myReq.responseText; document.getElementById('displayresult').innerHTML = result; } else { alert('There was a problem with the request.'); } } }
This JavaScript function first checks the state of the object; if it has completed its process, it next checks that the response code from the server was 200 (OK). If the response code is not 200, an alert is shown with the words “There was a problem with the request.” Any other responses will come from the execution of the add_bms.php script and will be displayed in a div with an id value of displayresult. For the moment, the displayresult id is defined in the new_ss.css style sheet as follows (a white background):
#displayresult { background: #fff; }
The following line of code has been added after the closing form tag in the PHP function display_add_bm_form(); this is the div in which the result from the server will be displayed to the user.
<div id="displayresult"></div>
Next, you will have to make modifications to the existing add_bms.php code.
Additional Modifications to Existing Code
If you were to attempt to add a bookmark without modifying anything in the add_bms.php script, the actual process of checking user permissions and adding a bookmark would work just fine. However, the result would be the monstrosity seen in Figure 34.4, which includes the duplication of the title, logo, and footer links, as well as other issues with the application display.
Figure 34.4 Adding a bookmark before editing the add_bms.php script.
In the non-Ajax version of the PHPbookmark application, remember that the form is one page, the submission result is another, and all page elements are reloaded at all times. However, in this Ajax-enabled environment, you want to add a new bookmark, get the result from the server, and continue on to add more bookmarks (or not) without reloading any page elements. This new functionality necessitates some changes to the add_bms.php code. The original code is shown in Listing 34.8.
Listing 34.8 The Original PHP Code in add_bms.php
<?php require_once('bookmark_fns.php'); session_start(); //create short variable name $new_url = $_POST['new_url']; do_html_header('Adding bookmarks'); try { check_valid_user(); if (!filled_out($_POST)) { throw new Exception('Form not completely filled out.'); } // check URL format if (strstr($new_url, 'http://') === false) { $new_url = 'http://'.$new_url; } // check URL is valid if (!(@fopen($new_url, 'r'))) { throw new Exception('Not a valid URL.'); } // try to add bm add_bm($new_url); echo 'Bookmark added.'; // get the bookmarks this user has saved if ($url_array = get_user_urls($_SESSION['valid_user'])) { display_user_urls($url_array); } } catch (Exception $e) { echo $e->getMessage(); } display_user_menu(); do_html_footer(); ?>
The first line of the script brings all the items of the bookmark_fns.php file into play. If you look at the contents of bookmark_fns.php you will notice that it calls yet another series of files:
<?php // We can include this file in all our files // this way, every file will contain all our functions and exceptions require_once('data_valid_fns.php'); require_once('db_fns.php'); require_once('user_auth_fns.php'); require_once('output_fns.php'); require_once('url_fns.php'); ?>
Although you may or may not need all the items in these files in the Ajax-enabled version of the bookmark addition sequence, the comment at the beginning says it all—every file will contain all our functions and exceptions. In this situation, as you are moving from a series of dynamic pages to all-in-one Ajax-enabled functionality, it is better to have a few extra elements than to remove core functionality before you are sure you don’t need it. Keep the first line of add_bms.php as is.
The second line, which begins or continues a user session, should also remain as is; even in the Ajax-enabled version of this action you will want some sense of security intact. Similarly, the third line can remain as well. This line gives the shortname $new_url to the POST value sent through the request:
$new_url = $_POST['new_url'];
Finally, you are at the point in which you can remove something, namely this line:
do_html_header('Adding bookmarks');
Because you are already on a page (add_bm_form.php) that contains HTML header information, there is no need to repeat it again—you’re not moving to a different page. This repetition produces the two sets of header graphics and titles that you see in Figure 34.4. For similar reasons, you can eliminate two lines at the end of add_bms.php as well:
display_user_menu(); do_html_footer();
If you remove these elements, upload the file to the server, and attempt to add another bookmark, the results will be closer to what you expect, although there are still some changes to be made. Figure 34.5 shows the application display with changes made in the code to this point.
We still have a duplicate message regarding the status of the user as “logged in,” but the issues are not nearly as unappealing as before. The next step is to remove the duplicate messages and to change some of the other exceptions-related functionality so that it makes sense in an Ajax-environment.
Figure 34.5 Adding a bookmark after the first pass of editing the add_bms.php script.
To remove the duplicate message regarding the user’s login name, delete this line from add_bms.php:
check_valid_user();
The check for valid user will have already been done when the add_bms_form.php page was loaded; you wouldn’t be on the page that invokes Ajax functionality if you weren’t found to be a valid user.
The next step is to remove the outer try block and the exception handling. The reason for this is because you want the script to get to the end in which the list of URLs already stored is displayed for the user. This means some adjustments will be made along the way to reintroduce error text as necessary. Listing 34.9 shows an amended version of add_bms.php.
Listing 34.9 An Amended Version of add_bms.php
<?php require_once('bookmark_fns.php'); session_start(); //create short variable name $new_url = $_POST['new_url']; //check that form has been completed if (!filled_out($_POST)) { //has not echo "<p class=\ "warn\ ">Form not completely filled out.</p>"; } else { // has; check and fix URL format if necessary if (strstr($new_url, 'http://') === false) { $new_url = 'http://'.$new_url; } // continue on to check URL is valid if (!(@fopen($new_url, 'r'))) { echo "<p class=\ "warn\ ">Not a valid URL.</p>"; } else { //it is valid, so continue to add it add_bm($new_url); echo "<p>Bookmark added.</p>"; } } // regardless of the status of the current request // get the bookmarks this user has already saved if ($url_array = get_user_urls($_SESSION['valid_user'])) { display_user_urls($url_array); } ?>
This version of the script still follows a logical path through possible events, but displays an appropriate error message without duplicating any other page elements.
The first check is whether the form itself has been filled out. If it has not, an error message is displayed between the addition form and the user’s current list of stored bookmarks. You can see this response in Figure 34.6.
The second check is whether the URL is properly formed; if it is not, the string is transformed into a proper URL and moves on to the next step. In the next step, a socket is opened and the URL is tested for validity. If it fails, an error message is displayed between the addition form and the user’s current list of stored bookmarks. However, if the URL is valid, it is added to the user’s existing list of stored URLs. In Figure 34.7 you can see the response when attempting to add an invalid URL.
Figure 34.6 Attempting to add a blank value.
Figure 34.7 Attempting to add an invalid URL.
Finally, and regardless of the errors in attempting to add a URL, the user’s existing bookmarks are displayed. You can see this result in Figure 34.8.
Figure 34.8 Success—a valid URL has been added.
Although the core functionality around adding a bookmark has been successfully Ajax-enabled, a few elements still need some work. For instance, the add_bm() function in the url_fns.php file contains some exceptions that could be handled differently to produce an error message in this new system. Listing 34.10 shows the existing add_bm() function.
Listing 34.10 Existing add_bm() Function in url_fns.php
function add_bm($new_url) { // Add new bookmark to the database echo "Attempting to add ".htmlspecialchars($new_url)."<br />"; $valid_user = $_SESSION['valid_user']; $conn = db_connect(); // check not a repeat bookmark $result = $conn->query("select * from bookmark where username='$valid_user' and bm_URL='".$new_url."'"); if ($result && ($result->num_rows>0)) { throw new Exception('Bookmark already exists.'); } // insert the new bookmark if (!$conn->query("insert into bookmark values ('".$valid_user."', '".$new_url."')")) { throw new Exception('Bookmark could not be inserted.'); } return true; }
In this situation, all we want to do is change the exceptions to produce error messages and continue the processing (display). This can be done by changing the two distinct if blocks to the following:
if ($result && ($result->num_rows>0)) { echo "<p class=\ "warn\ ">Bookmark already exists.</p>"; } else { //attempt to add if (!$conn->query("insert into bookmark values ('".$valid_user."', '".$new_url."')")) { echo "<p class=\ "warn\ ">Bookmark could not be inserted.</p>"; } else { echo "<p>Bookmark added.</p>"; } }
This version of the script still follows a logical path through possible events and displays appropriate error messages. After checking to see whether the bookmark already exists for the user, either an error message is displayed between the addition form and the user’s current list of stored bookmarks, or the script attempts to add the bookmark.
If the bookmark cannot be added, again an error message is displayed between the addition form and the user’s current list of stored bookmarks. However, if the bookmark is successfully added, the message “Bookmark added” is displayed. This echo statement has been removed from the add_bm.php script and put in this position in the add_bm() function because otherwise the user could have seen an error message such as “Bookmark could not be inserted” followed by a success message of “Bookmark added,” even though the outcome was not successful.
Figure 34.9 shows the result of these changes.
Figure 34.9 Attempting to add a bookmark that already exists.
Additional Changes to PHPbookmark
Changing the bookmark addition functionality to an Ajax-enabled user interface is just the first of many changes you could make to this application. The next logical choice might be the bookmark deletion functionality. The process might go something like this:
- Remove the Delete BM link from the page footer.
- Invoke a new JavaScript function when the user checks the Delete? check box next to a bookmark.
- Modify the delete_bm.php script so that it can be invoked by the new JavaScript function, complete the deletion process, and return a message to the user.
- Make any additional modifications to the functionality to ensure that actions occur and messages are displayed appropriately within the new user interface.
With the structure already in place for these changes, you should be able to make them just with the information provided in this chapter. However, the following sections provide links to resources containing much more information on creating Ajax-enabled sites.
Remember, Ajax is a set of technologies that work together to create a more fluid user experience; this often necessitates rethinking an application from the ground up now that you know what you can do when all the puzzle pieces fall into place.