Webforumz Newsletter - February 2008

Tutorials

Pimping A PHP Login/Registration System With AJAX

Introdution

For the December issue of Creative Coding I wrote an article on Login/Registration With PHP Sessions. This week we will be pimping this system with some AJAX. I recommend working through the first tutorial or at least using it as reference, otherwise this could become a little confusing.

For many people AJAX is a big scary acronym for some kind of Web 2.0, high-end, web development voodoo. I thought so too until a good friend of mine showed me how simple it can be. All that AJAX does is send a request "behind the scenes," using Javascript and XML, to the server. At the moment, this may seem like a formidable task, but the basics are easy and the results rewarding. You'll need this ZIP file with the pimped files from December's tutorial.

Like our last tutorial, we must maintain the accesibility of our application. Of course, if the user has Javascript disabled, then the AJAX also won't work. But, that doesn't mean that our application doesn't work.

index.php

The HTML

The HTML in index.php hasn't changed much. We still have two forms - login and registration - which can be toggled. I've added a few id attributes to some form elements to make them easier to access using JS and I've also added an error div:

<div id='error'>
        <div id='error_content' class="error"> </div>
        <div id='tip'> </div>
</div>
This div is styled using CSS with display:none; so that it is not visible when the page loads. We then use JS to display it to notify the user of errors that occurred.

The most important changes to the HTML are in the onsubmit attributes of the forms:

return checkLogin();

return checkRegistration();
These functions are defined in the functions.js file and we will be using them to handle user input. Both functions return false which prevents the forms from submitting.
The PHP

The PHP has also undergone some maintenance. The code for validating user input has been moved into an external file which is included near the top of the page:

// include php functions
$included=true;
include('./functions.php');
Because the functions.php file is also used by the AJAX requests, we set the $included variable to true before included the file (I'll explain that later).

We then initialize the $user object and check if something was posted. This code will only be executed when the user has JS disabled. All we do is call the PHP functions and check the return values for errors. If the return value is not '0' then something went wrong. The checkRegistration() can return more than one error, so we use explode() to get all the error codes.

functions.php

if($included!=true) {
This checks if the file was included by index.php or if it was called directly by an AJAX request. If it was called independently, then it first initializes the session, checks $_POST['action'] to see what we are trying to do (login or registration), echo()'s the return value of the called function and then stores the $user in the session and closes the database connection. If it was included, then it just serves as an external file with function definitions:
checkLogin()

This function attempts to log a user in using the $_POSTED values. It returns '0' upon success or an error code if the user does not exist or the password is incorrect.

checkRegistration()

This function attempts to register a new user. Like the checkLogin() function, it also returns '0' upon success or one or more error codes upon failure.

getErrorMessage()

This function is simple: It's only parameter is an error code number and it returns a string describing the error.

user.php

The user class hasn't changed at all. See December's tutorial for reference.

functions.js

The poo has hit the fan! This file contains the Javascript functions for sending and handling the AJAX requests.

The first five functions are explained in December's tutorial....

// declare AJAX request variable
var http_request;
Here we declare the variable that will store the AJAX request. It needs to be declared globally so that all the functions can access it.
newHTTPRequest()

This is the most important part of the system - creating a new AJAX request. For any normal browser it is as easy as:

new XMLHttpRequest();
Unfortunately, we have IE, so a bit more code is necessary:
try {
        return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
        try {
                return new ActiveXObject("Microsoft.XMLHTTP");
        } catch (e) {}
}
The try, catch code blocks are necessary to handle any errors. If we can't create a new request, we return false so that the form is submitted and can be handled by index.php.
showErrorMessage(top,text)

This function pops up the error div to display an error to the user. The first parameter is the margin-top value (in pixels) for the div so that it "points" to the right field. The second parameter is the text to display.

checkLogin()

Before we send an AJAX request, we can perform some basic tests client-side. Like checking if a username was entered:

// check if login username is empty
if($('login_name').value=="") { // no login name
        showErrorMessage(16,'Please enter a username!'); 
        // and pop the error message
}
If this check passes, then we can initialize a new AJAX request by calling the newHTTPRequest() and storing the return value in the http_request variable. After checking if the request was successfully created, we can initialize it:
http_request.onreadystatechange=refreshLoginForm;
This tells the request which function should be called when the status of the request changes (like a Windows event handler). An AJAX request can have five different statuses: 0 = uninitialized, 1 = loading, 2 = loaded, 3 = interactive, 4 = complete. Then we open the request:
http_request.open('POST','functions.php',true);
The first parameter is the method - we are using POST, the second is the URL to which the request should be sent and the third parameter specifies that the request should be sent asynchronously which means that the script does not wait for a response; execution continues like normal. The next step is setting the header of the request:
http_request.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
The two parameters are a label and its value. We are setting the Content-Type of the request to the default content type for a HTML form. See W3C for more info. And then we can send the request:
http_request.send('action=login&login_name='+$('login_name')
.value+'&login_pass_md5='+$('login_pass_md5').value);
The only parameter is a string containing the content of the request: the values from the form. We then return false to prevent the form from submitting normally and then we are finished and can sit back and twiddle our thumbs till the server response arrives....

refreshLoginForm()

This function handles the status changes from the login form AJAX request. So, the first thing we have to do when the function is called is check the status of the request:

if (http_request.readyState == 4) {
Anything other than '4' is uninteresting. If the status is "complete," then we check the HTTP status of the request:
if (http_request.status==200) {
Anything other than "200" (like a "404 - Page not Found" or a "403 - Forbidden") means something went wrong server-side so we output an error and the script exits. But, if everything is ok, then we can get the response
var result=http_request.responseText;
and check the returned code. If there was an error, show the error popup, else, let the user know that have been logged in.

checkRegistration()

This function is very similar to the checkLogin() function. After a few basic checks, it initializes and sends the AJAX request and we can twiddle our thumbs again....

refreshRegistrationForm()

Again, similar to refreshLoginForm(), this function is called when the registration form AJAX request changes its status. It checks the request status, HTTP status and then outputs the result to the user.

The End

That's all there is to it! We have mastered the art of Asynchronous JavaScript and XML. You have officially been pimped!