All Things Patrick - Working Hard to be Lazy!

  • home
  • blog
  • links
Home › Blogs › patrick's blog

Diving into AJAX Tutorials, part 1

patrick — Mon, 2006-11-13 08:25

For the past year there's been alot of AJAX hype & I figured it was about time that I started brushing up on "AJAX". AJAX was coined in February of 2005 (referenced at wikipedia) by Jesse James Garrett to describe the technologies to a client. As I've had alot of free time lately I thought I'd go through several tutorials that I've come across....

I haven't done anything with AJAX in a number of years. Actually the last time I really did much of anything with AJAX was before it was known as AJAX. The interesting thing is how I discovered XMLHTTP (one of the technologies behind AJAX). It was back in the <sarc>"good ol’ days"</sarc> of me programming in Visual Basic. I was trying to find function references and how to do stuff using Microsoft's MSDN. The navigation system fascinated beyond what it should have & I decided to look at how it worked.

I started off looking for the html or the javascript array that held all the items on the menu. Nothing turned up - in *any* of the html or javascript files. I started digging through the code, reading over it line by line & skipping the easy stuff I already knew (control structures, predefined function calls, etc). At one point I came across this weird XML object that used a URI file reference. That was weird. I had never seen any javascript ever load an external file. I pulled up the entire URL in a new browser window & was presented with an XML tree that had all of the navigation menu items & they were wrapped around weird elements that had what seemed to be non-sensical attributes & attribute values.

I ended up copying all of the code to my computer and started playing around with values. I ended up putting together a small XML file myself and seeing how that worked. It was fascinating, but unfortunately, at that time, only worked on Internet Explorer 5.0 (which was released in 2000). Other browsers had started incorporating it in 2002, but by that time I had already stopped using it as I had started discovering cross-platform/cross-browser bliss.

 

First off I thought I'd go through various issues of .net (dot net magazine) as the articles had looked promising.

The first issue I have with any AJAX coding articles is March 2006, #147 - "AJAX power" & claims to have "fully documented code examples inside" as well as "all your questions answered". It's a 7 page article with a bunch of code samples & blah blah text between describing what's going on. There's also a bunch of sidebars through out -

  • Find out more about AJAX (urls for 5 AJAX frameworks/engines)
  • Five cool AJAX implementations (doesn't mention google maps, but they do look good)
  • Expert answers to AJAX questions (see below)
    • do I have to use XML with AJAX [no]
    • could AJAX replace Flash [no, they're different technologies]
    • how is AJAX changing the web [it's giving new responsiveness blah blah blah]
    • why is AJAX becoming so popular when it has been possible for many years [in my opinion they gave a bad answer - it's really because of the acronym & the hype, people have been using it before it was just hidden... not to mention several major companies have picked up on the hype to explain what they were already working on]
    • can AJAX be an enterprise level solution [it can be a part of a solution]
  • Simple AJAX mistakes to avoid
    • breaking the back button
    • no feedback when content is updated
    • unavoidable weaknesses
    • no confirmation
    • spider blocking

 

Here's a listing of the final versions of the files as well as links to the code for them
  • index.php (figure 6)
  • js/ajax.js (figure 7)
  • js/request.js (figure 8)
  • services/post.php (figure 3)
  • classes/post.class.php (figure 5)
  • /hidden/mysql_connect.php (figure 9)
  • mysql.sql (figure 10)

 

Ok, so now it's time to dive into the code samples & see what we have. The first code sample left off the DOCTYPE & html element, but I can understand that as it's pretty easy to put in myself. There are a bunch more code samples with alot of text detailing what's going on. Most of what's listed are functions. Nothing object-oriented, but everything is fairly self explanatory.

Then we reach Creating an object-oriented AJAX engine. All of the code is fairly well done & includes a toString() method, but nowhere does it mention what to name the file this object should be in. I did notice on the next page an HTML fragment that includes "js/Ajax.js", so I made a js directory and saved this javascript object in there.

Next grouping of code creates an instance of the AJAX object and adds a global onResponse() function which checks the AJAX object's checkReadyState(...) method. When the browser's XMLHttpResponse object returns a finished loading state, onResponse() pulls the information out of the retrieved XML & dumps it to the page using .innerHTML attribute of the target element. Okay, so I understand that this is a magazine article & these are code "samples". However, the novice programmer who's never used DOM before will think this is standard operating procedure, won't investigate how he should really program this, & leave it as the code sample says (which could lead to problems down the road with cross-browser issues &/or newer technologies that require DOM compliance...).

The next section is Database interaction with AJAX and PHP. Here's that HTML fragment I was referring to earlier & it also tells us the name of the PHP file we're going to be creating. Before we get to the PHP though there's a bit of SQL that we need to run on the database to create a table to store the information. The bottom of the page has 1 line of PHP, and we flip the page & there's more PHP. But wait! 1 of the functions refers to this! We're creating a PHP object... ok, I flip the page back to see if I missed the class name. Nope, only the 1 line of PHP. Was it in the surrounding text? Nope, the previous paragraph & the code sample state -

Now that the table is created we can make the server-side file that will do the direct communication with the MySQL database table. We'll first need to create a way for connecting to the database, which consists of two methods and a property:
var $table;

public function Post()
{
require_once('path to.../mysql_connect.php');
$this-table = 'net_introtoajax';
}

private function dbConnect()
{
DEFINE ('LINK', mysql_connect (DB_HOST, DB_USER, DB_PASSWORD));
}

.

Okay, well none of that is helpful. I go through all the rest of the code samples in the magazine. The 4th code fragment has what I need, but in a very round about sort of way -
An additional PHP file needs to be created to make a bridge between the AJAX engine and the server-side script that handles the database communication. Below is the code used to create this file:
require_once('../classes/Post.class.php');
$post = new Post();
$post->$method($item);
.
Okay, so now I can add the class code to the "Post.class.php" file (now that we also have a filename). However I notice something very frightening with this last code fragment. It assumes that PHP's register_globals option is turned on. This can cause all sorts of problems when somebody starts manipulating the URL string to overwrite variables in your application so *most* developers that are aware of it have it turned off (and most web hosting solutions have it turned off as well). The PHP Manual entry on register_globals discusses this issue, but for somebody just glancing over the page, they might not understand what the big deal is. Here's the code fragment (minus comments) posted under "Example misuse with register_globals = on" -
<?php
if ( authenticated_user() ) {
        $authorized = true;
}


....


if ( $authorized ) { include( '/highly/sensitive/data.php' ); } ?>
If register_globals = on and the URL for this page is "http://sample.com/mypage.php", then all somebody would need to do is go to "http://sample.com/mypage.php?authorized=true" to overwrite the $authorized variable & be able to see the "/highly/sensitive/data.php" contents. Now I hope it becomes clear as to why this is usually turned off.

So, no big deal I'll just modify the code a little so that it's a little more appropriate -

My code figure 1 (services/post.php)

<?php
require_once('../classes/Post.class.php');
$post = new Post();
$post->$_REQUEST['method']($_REQUEST['item']);
?>
Okay, back to page 36 where we need to define a bunch of variables for the database. It doesn't say what file these go in, but I'm making the assumption based on the public function Post() above in the file "classes/Post.class.php" that these go into a file called "mysql_connect.php". The odd piece here is a the line that states -
$dbc = @mysql_connect (DB_HOST, DB_USER, DB_PASSWORD)
    OR die('Could not connect to MySQL: ' . mysql_error() );
I know exactly what it does. That's not the issue that I have with it. My problem with it is based upon the fact that it does the exact same thing that the private function dbConnect() in "classes/Post.class.php" above! Only because of the @ it suppresses any errors & the or die(...) segment outputs the mysql_error() if there are any errors... weird, but ok on the suppression & then outputting the error. Duplication of effort where the dbConnect() function is concerned.

Two more code segments flesh out the save($item) & get() methods of the Post class. However, again I have an issue with the code presented. Yes, I know it's a magazine, this is sample code, & real programmers like me would know how to code this better... but again I screem negligence. Here's the save($item) method that is presented -

public function save( $item )
{
    $this->dbConnect();
    $query = "INSERT INTO $this->table (item) VALUES ('$item')";
    $result = @mysql_query( $query );
    mysql_close();
    $this->get();
}
.
Okay, so what's wrong with this you might ask. The problem is the insecurity of the value of $item. This code assumes the whole hearted goodness of the soul that submited $item. The other problem creeps up when somebody even innocently enough adds an apostrophe. Of course this may well depend as to whether yet another PHP variable is turned on or off. PHP has this nifty thing called "Magic Quotes" (magic_quotes_gpc) which, when turned on, automatically escapes single quotes, double quotes, backslashes, and null characters - affecting HTTP Request data (GET, POST, & COOKIE data). So what does this mean?

This means that if someone where to type the following -

I'm able to see "Gone with the Wind" today
Then the variable $item would be equal to -
I\'m able to see \"Gone with the Wind\" today
Unfortunately you can't always be sure that magic_quotes_gpc = on so you have to escape things the long way. Not only that, but the preferred, more secure way, is to escape a string specifically for the database you'll be using. Here's something more along the lines of the way this should have been written -

My code figure 2 (fragment from classes/Post.class.php)


>public function save( $item ) { $this->dbConnect(); if( get_magic_quotes_gpc() ) { $item = stripslashes( $item ); } $item = mysql_real_escape_string( $item ); $query = "INSERT INTO $this->table (item) VALUES ('$item')"; $result = @mysql_query( $query ); mysql_close(); $this->get(); }The last 2 code segments listed are javascript and are added to the onResponse() function and added to the Ajax class. Now it's time to load up the page & see how all of this works. Initially there's nothing there - that's expected as nothing has been added to the database yet & so there's no items to list. I type something in & press the "save this item" button & nothing. Okay, that's weird. I open up Firefox's error console, hit the clear button to remove everything & start fresh, hit the refresh button. No errors so far. I type something into the text box, hit the "save this item" button again, & again nothing. Not only that, but there aren't any errors showing up in Firefox's error console which means the error is not a javascript error. That's the good news...

I have it load services/post.php & it spits out a blank file. That's not right... I look again at post.php & notice there's no default method, so I change the URL to point to services/post.php?method=get. Still a blank page. I put print( 'running method xxxx' ); at the beginning of all the methods in classes/Post.class.php & it still prints out a blank page... Why aren't I getting any errors? I check my apache vhosts file (for http://patrick.blogdns.org) & notice "php_value display_errors 0" (error reporting has been turned off). So I set up a new apache vhost (http://ajax.patrick.blogdns.org) for my ajax projects & set "php_value display_errors 1", set the root directory to my ajax projects directory, navigate back to services/post.php?method=get, & get a page with an error. It can't figure out what I'm trying to do.

Yay for php.net/manual. I pull up the page on Classes and Objects (PHP 5) & using the browsers search functions do a page search for "->$" to see if anybody has posted comments on trying to use a variable to access a class method. Most of the examples show how to access class attributes, but one goes into quite alot of detail.

..... You can use members of other objects as well:
<?php
print $foo->{$otherObj->var};
print $foo->{$otherObj->func()};
?>
You can use mathods above to access member functions as well:
<?php
print $foo->{'aMemberFunc'}(); // Prints "Inside `aMemberFunc()`"
print $foo->{$foo->aFuncName}(); // Prints "Inside `aMemberFunc()`"
?>
.
Okay, so apparently that's yet another difference between PHP 5 & prior versions... so I modify services/post.php -

My code figure 3 (services/post.php [final])

<?php
require_once( '../classes/post.class.php' );


if ( !isset( $_REQUEST['method'] ) ) { $method = 'get'; } else { $method = @$_REQUEST['method']; } $item = @_REQUEST['item'];


$post = new Post(); $post->{ $method }( $item ); ?>
I added the check for a method so I could have the code put a default method in there if I forgot while I was testing. I also lower cased the "classes/post.class.php" filename because the capital letter was driving me nuts (I'm lazy & it helps me be lazy when I'm using a console). Technically I didn't have to create the $method & $item variables, but I wanted the last lines to look like the tutorial as well as keep it open for possible future changes. Not only that, but the PHP compiler is smart enough to not make a copy of a variable until something is actually modified. Until the new (or old) variable is modified, the copy is simply a pointer to the original variable.

Now I reload the services/post.php?method=save&item=item 1 URL &... more errors. Now that it's actually trying to do something with the instantiated Post object, all the internal errors are now coming to the surface. Okay, let's start by trying to figure out why it's defining the LINK constant twice as that may solve some of the other MySQL errors.

private function dbConnect() {
        DEFINE ('LINK', mysql_connect (DB_HOST, DB_USER, DB_PASSWORD));
}
At first I thought there might be several files trying to define 'LINK', but after grep'ing all the files in all of the directories for this small project I only found the 1 file. So the error has to be with that code fragment above. Ah, both the public function save($item) & public function get() call $this->dbConnect(); right off the bat. Not only that, but the last thing that public function save($item) does is call get()! So this only becomes an issue if you're saving something, but not if you're trying to retrieve stuff. Best thing to do instead of defining a global variable (since we're inside an object) is to define a class variable $link & then change the code inside dbConnect() to assign the database resource returned from mysql_connect to it. I'm also going to have it return an error (using or die() even though for a normal project it should return the error, but I've wasted enough time on this tutorial already).

My code figure 4 (fragments from classes/post.class.php)

    $link;
    ....
    private function dbConnect() {
        $this->link = mysql_connect( DB_HOST, DB_USER, DB_PASSWORD )
            or die( 'Could not connect to MySQL: ' . mysql_error() );
    }
Now that I have that error taken care of there's some more errors. Considering how many errors there are I'm going to see if I can deal with just getting a list of items in the database. I pull up phpmyadmin for that server, head into the database and table that were created, & add something. Now I had back to the URL services/post.php?method=get. It's saying that mysql_fetch_array in get() does not have a valid MySQL result set. Great, so I head to php.net/manual, copy & paste mysql_fetch_array into the search box & press enter which takes me to the mysql_fetch_array page. I dig through that & it seems like everything is ok there. I look through my code some more to find out where $result came from (it's the result set that PHP is complaining about) -
$result = mysql_db_query (DB_NAME, $query, $this->link);
Best thing to do there is click the link on the php.net manual page for mysql_db_query. I look over everything & everything seems to be ok as far as parameters are concerned. I check my SQL query to see if I've made any mistakes there, no problems there either. I scroll down the PHP manual page for mysql_db_query to see the examples & browse over user comments, but before I even get there I see this -
Version Description
4.0.6 This function is deprecated, do not use this function. Use mysql_select_db() and mysql_query() instead.
Beautiful! Checking the PHP release page we find that version 4.0.6 was released 23 June 2001. A tutorial dated March 2006 is using a function that's been deprecated for over 5 years! Now I can fix that & see what else needs fixing. Now it's complaining that mysql_query in save($item) doesn't have a valid resource link. Ok, we just went through this with mysql_db_query so what's going on? Looking closer at the code I realize that no database has been selected! This entire class needs to be rewritten for it to truly be good, but at this point I just want to get the blasted PHP to work so I can see "the power of AJAX"... wasn't this article about AJAX & not about PHP?

My code figure 5 (classes/post.class.php [final])

<?php
// pulled from hidden libs directory on server
require_once( 'mysql_connect.php' );
@define( 'NL', "n" );   // my own defined newline character
@define( 'TAB', "t" );  // my own defined tab character


class Post { var $table; var $link;


public function Post() { $this->table = 'ajax_dotnetintro'; }


private function dbConnect() { // define( 'LINK', mysql_connect( DB_HOST, DB_USER, DB_PASSWORD ) ); $this->link = mysql_connect( DB_HOST, DB_USER, DB_PASSWORD ) or die( 'Could not connect to MySQL: ' . mysql_error() ); }


public function get() { $this->dbConnect() $query = 'SELECT * FROM ' . $this->table . ' ORDER BY id'; // $result = mysql_db_query( DB_NAME, $query, LINK ); mysql_select_db( DB_NAME, $this->link ) or die( 'Could not select MySQL database: ' . mysql_error() ); $result = mysql_query( $query, $this->link );


// why would you declare this to be ISO-8859-1 & then in the // Content-Type declare it to be UTF-8 ?? // $xml = '<?xml version="1.0" encoding="ISO-8859-1" ?>'; $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . NL; $xml .= '<list>' . NL; while( $row = mysql_fetch_array( $result ) ) { $xml .= TAB . '<items>' . NL; $xml .= TAB . TAB . '<item><![CDATA[<li>' . $row['item'] . '</li>]]></item>' . NL; $xml .= TAB . '</items>' . NL; } $xml .= '</list>'; mysql_close();


header( 'Content-Type: application/xml; charset=UTF-8' ); echo $xml; }


public function save( $item ) { $this->dbConnect(); mysql_select_db( DB_NAME, $this->link ); or die( 'Could not select MySQL database: ' . mysql_error() );


if( get_magic_quotes_gpc() ) { $item = stripslashes( $item ); } $query = 'INSERT INTO ' . $this->table . ' (item) VALUES ('' . mysql_real_escape_string( $item ) . '');' ; $result = @mysql_query( $query ); mysql_close(); $this->get(); } } ?>
Ok, now I'm finally getting somewhere. Now I can go back & test the page & see if it's actually working now. The initial page displays the 1 item I had previously put in the database as well as the other I was using to try to fix the save page (twice as I got it to work & then wanted to make sure it worked). Just to make sure, I type something into the form box & press the "save this item" button & the page blinks as it refreshes the content with the new item at the bottom.

Considering how much work I had to put into the previous files to get this "simple tutorial" to work, not to mention alot of my own modifications to the files presented in the tutorial (to get to meet standards, etc) - I think I should be able to post the other files here as well.

My code figure 6 (index.php [final])

<?php
print( '<?xml version="1.0" encoding="UTF-8"?>' );
$pagetitle = 'Object Return Pattern';
?>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" > <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title> <?php print( $pagetitle ); ?> </title> <script type="text/javascript" src="js/ajax.js"></script> <script type="text/javascript" src="js/request.js"></script> <script type="text/javascript" > function doLoad() { ajax.makeRequest( 'POST' , 'services/post.php?method=get' , onResponse ); } </script> </head>


<body onload="javascript:doLoad();">


<ol id="list"></ol>


<br /><br /> <form method="post" action="./" onsubmit="saveNewItem(); return false;" > <b>Add a new list item:</b> <br /> <input id='item' /> <br /> <input type="button" name="submit" value="save this item" onclick="javascript:saveNewItem();" /> </form>


</body> </html>

My code figure 7 (js/ajax.js [final])

function HTTP() {
    this.toString = function() { return 'HTTP'; }


this.status = function( _status ) { switch( _status ) { case 200 : return "success"; break; case 404 : return "File not found."; break; default: return "HTTP Status: " + _status; } } }


// requires the js object HTTP function Ajax() { this.toString = function() { return "Ajax"; } this.http = new HTTP();


this.makeRequest = function( _method, _url, _callbackMethod ) { // single line if() {} this.request = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("MSXML2.XMLHTTP") ;


this.request.onreadystatechange = _callbackMethod; this.request.open( _method, _url, true ); this.request.send( _url ); }


this.checkReadyState = function( _id, _1, _2, _3 ) { switch( this.request.readyState ) { case 1 : document.getElementById( _id ).innerHTML = _1; break; case 2 : document.getElementById( _id ).innerHTML = _2; break; case 3 : document.getElementById( _id ).innerHTML = _3; break; case 4 : document.getElementById( _id ).innerHTML = ''; return this.http.status( this.request.status ); } } }

My code figure 8 (js/request.js [final])

var ajax = new Ajax();
function onResponse() {
    if (
        ajax.checkReadyState(
            'list'
            , 'loading...'
            , 'loading...'
            , 'loading...'
        ) == "success"
    ) {
        if ( ajax.request.responseXML == null ) {
            document.getElementById( 'list' ).innerHTML =
                "There are no list items available.";
        }
        else {
            document.getElementById( 'list' ).innerHTML = '';
            var response = ajax.request.responseXML.documentElement;
            var items = response.getElementsByTagName( 'item' );
            for ( var i = 0; i < items.length; i++ ) {
                document.getElementById( 'list' ).innerHTML +=
                    items[i].firstChild.data;
            }
        }
    }
}


function saveNewItem() { var _item = document.getElementById( 'item' ).value; ajax.makeRequest( 'POST' , 'services/post.php?method=save&item=' + _item , onResponse ); }

My code figure 9 (/hidden/mysql_connect.php [final])

<?php
define( 'DB_USER', 'myusername' );
define( 'DB_PASSWORD', 'mypassword' );
define( 'DB_HOST', 'localhost' );
define( 'DB_NAME', 'database' );


/* $dbc = mysql_connect( DB_HOST, DB_USER, DB_PASSWORD ) or die( 'Could not connect to MySQL: ' . mysql_error() ); */ ?>

My code figure 10 (mysql.sql [final])

DROP TABLE IF EXISTS `ajax_dotnetintro`;
CREATE TABLE `ajax_dotnetintro` (
    `id` int(11) NOT NULL auto_increment,
    `item` varchar(250) NOT NULL default '',
    PRIMARY KEY (`id`)
) ENGINE=MyISAM COMMENT='.net magazine #147' DEFAULT CHARACTER SET utf8;
Unless I made a typo somewhere while converting the data to display here, all of this code should work. My LAMP setup consists of Debian Linux (Etch - kernel 2.6.8-2-k7, debian 1:3.3.5-12), Apache 2 (2.0.55-4.1), MySQL 5.0 (5.0.24a-9), & PHP 5 (5.1.6-1).

I have to say this was a good exercise in getting PHP files to work... As for the AJAX side of things - there was a couple of useful tidbits, but I'll go elsewhere for a real set of AJAX tools. No debugging of the javascript files was needed for IE or Firefox... I haven't bothered testing this in other browsers yet & not sure I'll ever get around to testing in other browsers.

Trackback URL for this post:

https://blog.whitelionsoft.com/trackback/36
  • HTML
  • JavaScript
  • PHP
  • patrick's blog
  • Add new comment

User login

What is OpenID?
  • Log in using OpenID
  • Cancel OpenID login
  • Create new account
  • Request new password

Pages

  • About Me
  • About My Boxen
  • Quotes
  • 3d Graphics
  • Color Picker App
  • Resume
  • dailymile
  • facebook
  • twitter

Tags in Tags

CSS Design Development Entertainment EVE-Online Firefox Freelance Friends Games Hardware HTML Internet Explorer JavaScript Job Movies & TV Operating Systems Personal PHP Security Windows
more tags

Blogroll

  • 456 Berea Street
  • Anne van Kesteren’s Weblog
  • Clients From Hell
  • Derick Rethans' blog
  • Doug Seitz
  • Eric Meyer
  • Terry Chay
  • The FAIL Blog

Powered by Drupal, an open source content management system
  • home
  • blog
  • links