AJAX Tutorial

Write your "How To" regarding anything you are comfortable with and you feel it will help the forum members.

NOTE :: All threads started here will appear only after the approval from Administrator
Post Reply
rohan2kool
Posts: 9
Joined: Thu Apr 13, 2006 5:24 pm
Contact:

AJAX Tutorial

Post by rohan2kool »

Image
Prologue: The thing that is AJAX

AJAX is one the those things which are re-invented only to find that this time they have become the biggest show-stealers and the hottest topics for forums, blogs and various sites. Basically, what is AJAX. AJAX as you may know, stands for Asynchronus JavaScript And XML. The fullform is useless because it is hardly close to the definition pertaining to which I've made this tutorial. AJAX, is a technology or a method which enables a web-page to contact the server from within the page without causing the browser to load any other page, reloading or any sort of redirection. By 'contact' the server I mean, to contact the server with params and in return get data.

Buying books on AJAX is useless. AJAX is not a topic so vast that it requires a book. There is a lot of filler-stuff filled in those books like "AJAX and bandwidth", "What AJAX can do", "What AJAX can't do". This is just an author's attempt to reach to the 'word-limit' for their works to be called 'books'. Out of it, 70% of the information everyone knows, and for the rest, no one wants to know that.

Prologue 2: Getting down to AJAX

So.. let's get down to some AJAX. AJAX consisits of two parts:

1. Server side
2. Client Side

Server Side is coded via php, asp, jsp etc. In this tutorial, we'll use php and MySQL. Client-side is obviously JavaScript. We will also be using a fair amount of xHTML as it is necessary for defining our pages.

The tools you need are:

1. A text-editor
2. A browser supporting XMLHttpRequest()
3. php and MySQL installed on your system + a webserver(recommended: Apache 2.****)
4. Basic knowledge of: JavaScript, php, SQL and (x)HTML

There are two ways of scripting AJAX:

1. XMLHttpRequest()
2. iFrames

iFrames method is not reliable and an iFrame cannot be controlled once it is set to fetch a server side request. Hence, we will be using XMLHttpRequest(). The browsers supporting it are:

1. Internet Explorer 6+ (through ActiveX)
2. Opera 8+
3. Firefox 1.00+

There are other browsers, but I am not aware whether or not they support this method. Please check your browser if it supports XMLHttpRequest().

Chapter 1: The grounds of our app

Let's first start with a generic xHTML page:

Code: Select all

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
  <head>
   <meta http-equiv="content-type" content="text/html" />
   <title>My first AJAX page</title>
  </head>

  <body>
   <p>Hello world</p>
  </body>
 </html>
Now, let's create a script object for inserting the AJAX code. Here is the modified <head> section of our HMTL page:

Code: Select all

<head>
 <script type="text/javascript">
 <!--
  //Our code comes here
 //-->
 </script>
</head>
Now, we come to the server-side. Here is the generic php code for outputting a simple string "AJAX was here":

Code: Select all

<?php
printf("AJAX was here");
?>
Now things are setup, we can go into AJAX.

Chapter 2: The XMLHttpRequest() object

The XMLHttpRequest() is a JavaScript object. It has the following methods:

Code: Select all

XMLHttpRequest.abort() -> Abort the XMLHttpRequest request
XMLHttpRequest.getAllResponseHeaders() -> Get all the headers from XMLHttpRequest request to the server
XMLHttpRequest.getResponseHeader() -> Get only the 'status' and 'readyState' headers from XMLHttpRequest request to the server
XMLHttpRequest.open() -> Open(Initialize) a XMLHttpRequest()
XMLHttpRequest.send() -> Execute the XMLHttpRequest()
It has the following event(s):

Code: Select all

XMLHttpRequest.onReadyStateChange -> Event executes whenever the request is available.
P.S: Actually, onReadyStateChange is a property of the object which has the value of the function which is to be executed when the request is available.

It has the following properties:

Code: Select all

XMLHttpRequest.readyState: The readyState header from the request. It has 4 possible values:
	0 Uninitialized: The object has not been initialized yet.
	1 Open: The open() method has been succesfully called
	2 Sent: The data has been sent, but the request return is yet awaited
	3 Receiving: The request data is now available
XMLHttpRequest.responseText: The text outputted by the server
XMLHttpRequest.****: Same as the above with the only difference that it has a content-type text/xml
XMLHttpRequest.status: Http status of the XMLHttpRequest(). Example -> 200(Page found without any http errors), 404(Not found), 500(Internal Server Error) etc.
If I have confused by now.. please forgive me. All this will be simplified as we go into scripting AJAX.

Chapter 3: The createRequestObject() function

We will now make our first steps towards AJAX scripting. A new XMLHttpRequest() object can be made quite simply:

Code: Select all

//JavaScript code

myAjax = new XMLHttpRequest();

//or the syntax can be said to be:

(object) = new XMLHttpRequest();
The browser wars have made things quite difficult for use developers. Hence we need to do a bit of work to overcome these problems. Opera and Firefox have no problems with this method. But we just cannot afford to neglect the people using Internet Explorer because it's user base is so huge, that neglecting means reducing your client base drastically. Internet Explorer uses XMLHttpRequest via ActiveX control. Hence, we will make a function in JavaScript that makes the XMLHttpRequest according to the browser. Here is the createRequestObject():

Code: Select all

<script type="text/javascript">
<!--
function createRequestObject() {
    var xhr;
    var browser = navigator.appName;
    if(!window.XMLHttpRequest){
		if(browser == "Microsoft Internet Explorer") {
			xhr = new ActiveXObject("Microsoft.XMLHTTP");
		} else {
			alert("Sorry, this page cannot be viewed on your browser i.e " + browser);
			return false;
		}
    } else {
        xhr = new XMLHttpRequest();
    }
    return xhr;
}
//-->
</script>
What this function does is, firstly it initializes two variables: xhr and browser. Then using the appName property of the 'navigator' object, it determines the name of the browser. Then it checks whether the current browser supports XMLHttpRequest or not. If it supports, then it assigns the value 'xhr' as a new XMLHttpRequest. If not, it checks if the browser is Internet Explorer. If it is, then it attempts to create an ActiveXObject called the XMLHTTP, which is actually similar to the XMLHttpRequest. Otherwise, it alerts the user that the page cannot be viewed on their browser. Now, in both the cases, xhr is the XMLHttpRequest object. In the final statement, we return the object called 'xhr'. So, now we can create an XMLHttpRequest like this:

Code: Select all

//JavaScript code

myAjax = createRequestObject();
Simple, ain't it?? Now we've learnt to make an XMLHttpRequest object across browsers easily. Let's move on to some real AJAX now...

Chapter 4: Doing some AJAX

Now, let's move to some practical things. Let's do some AJAX. This basic example here will be a simple example. You **** a button, the server is contacted and a string is returned. We will be using the code written in Chapter 1 as part of the setup.

4.1: The HTML page

This is the sample page we will use for dispalying this AJAX example:

Code: Select all

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
  <meta http-equiv="content-type" content="text/html; charset=iso-8859" />
  <title>My first AJAX page</title>
  <script type="text/javascript">
  <!--
  var httpr = createRequestObject();

  function createRequestObject() {
    var xhr;
    var browser = navigator.appName;
    if(!window.XMLHttpRequest){
		if(browser == "Microsoft Internet Explorer") {
			xhr = new ActiveXObject("Microsoft.XMLHTTP");
		} else {
			alert("Sorry, this page cannot be viewed on your browser i.e " + browser);
			return false;
		}
    } else {
        xhr = new XMLHttpRequest();
    }
    return xhr;
  }

  function fetchData() {
	httpr.open('GET', 'returndata.php');
	httpr.onreadystatechange = processData;
	httpr.send(null);
  }

  function processData() {
  	if(httpr.readyState == 4) {
  		if(httpr.status == 200) {
  			var data = httpr.responseText;
  			****.getElementById('fields').innerHTML = data;
  		}
  	}
  }
  //-->
  </script>
 </head>

 <body>
  <p id="fields">Text here will be changed</p>
  <input type="button" value="**** to change text" ****="javascript:fetchData()" />
 </body>
</html>
Save this file as ajaxmain.html and the php file as returndata.php. Your page should look like this:

Image

Try it on your server and see if it works or not. If you don't know php, you can try it with any other server side scripting language you know. If you don't know php, you can try this with a file too. For example, in the code just replace 'returndata.php' with the file of your choice. The contents of your file will be the responseText of the htppr object. Now, let us dissect the code and understand what it is..

Note: If you are using php or any server scripting, be sure to access these files through the server (via http://localhost or http://127.0.0.1)

4.2: Cracking the code

The function createRequestObject() is already clear to you. What we do in this script is we first, make a variable called httpr and make it a XMLHttpRequest() object through our createRequestObject() function. Then using the ****="javascript:fetchData()" HTML attrib, the function fetchData() is accessed whenever you **** on the button labelled '**** to change text'. The fetchData() function is very useful. This is what it does, line-by-line:

1. Initialize the XMLHttpRequest. The first param i.e 'GET' is the method of data transfer. We will be using GET for know as POST is used for transferring files and passwords(secure data). But, I will add that in the 2nd part of the tutorial.

2. Change the onreadystatechange (all small letters. JavaScript is absurdly case-sensitive) property to the function name you want to execute whenever it is available. In our case, it is processData(). Remember, we don't write it as httpr.onreadystatechange = processData() but we write it as httpr.onreadystatechange = processData. This is because the '()' are put only when calling or declaring a function. When assigning function to a property, we use the function as a variable, hence we don't put the two brackets and even if we do, it won't work. ;)

3. We finally send the request.

This was the anatomy of a simple XMLHttpRequest() example. Now, let us dig into the processData() function for the final steps. Here is what the processData() function does when it is called line-by-line.

1. Obviously, this function is called whenever the readyState of the request changes. Firstly, it checks if the readyState of the httpr object is 4. If not, nothing happens.

2. If it is 4, then it checks for the status of the httpr object. If it is 200, then it continues, otherwise again.. nothing happens. (You can add custom checks for a 404 status(page not-found error), 500 status(internal server error), 403 status(Forbidden Access error) etc.).

3. If everything's fine, it initializes a variable called 'data' and assigns it all that it has recieved from the request using the httpr.responseText property.

4. Finally, using generic JavaScript methods, we change the text of an HTML element with the id called 'fields' from whatever it was to whatever we got from the request.

The HTML code must be quite familiar to you and I need not explain that as I told you: You need basic knowledge of JavaScript for this tutorial.

Note: If you might have wondered why the variable httpr was declared out of everything... it was because the variable was to be used by two functions. Hence it needed to be a global variable. Had I decalred it inside the fetchData() function, it would have died the minute fetchData() was over and processData() wouldn't have been able to use it even if it had been alive.

Chapter 5: Some Trivia

I wanted to pack in another neater example.. but I'll do it on 5th June because of time constrains. Out of whatever I've taught you, this is most if the AJAX you'll ever use. If you are puzzled where the XML went.. let me tell you that I am against the idea of using XML for AJAX. There are two reasons for that:

1. If even one character of the XML data is not reached due to a communication failure or whatsoever, the parser would give a failure and you won't be able to get any message. But, if you use text like we have used in this message, you can atleast get some data from it. AJAX if not wisely used is a bandwidth killer. Hence, you need to make the most out of your request returns.

2. Supposing you have to get the data of a name and phone no. from the server. This is the XML way of doing it, so that the parser will parse it without any problems:

Code: Select all

<?xml version="1.0" encoding="iso-8859-1"?>
<person>
 <name>Somename</name>
 <phone>123456</phone>
</person>
<person>
 <name>Agoodname</name>
 <phone>789012</phone>
</person>
3. XML Parsers are sometimes slow and change as browsers change. Using AJAX libraries is another bad option as they are too complicated for their purposes.

Whereas you can also put it as text: Somename|123456|Agoodname|789012 and then take the odd text-pieces as names and even text-pieces as numbers seperated by a pipe i.e '|'. The first case used 176 characters, while the 2nd case uses only 32 characters. As I said.. bandwidth is necessary.

As for the trivia, data strings like the one above is usable. This is how:

Code: Select all

var data = "Somename|123456|Agoodname|789012"; //You can also get this via a httpr.responseText
var strings = new Array();
var phones = new Array();
strings = data.split('|');
/*Split the string into text-pieces seperated by a pipe symbol
for(var i=0; i<=strings.length; i++) {
/*if it's even, it's a phone no., if it's odd... it's a name*/
	if(i%2 == 0) {
		phones[] = strings[i];
	} else {
		names[] = strings[i];
	}
}
This way you can easily use this information. If needed you can make the server script to append a string called '|FEH' to all request returns. Then on the client side, you can check if the last element in the array is 'FEH'. If it is, then data has not been truncated on the way. Isn't it much better than XML Parsers?

You can use these methods for login systems, forum replies and what not. You can make the script print a 1 or 0 if the login passes or fails. Then on computing the returns, you can easily make out whether the login was succesfull or not. However, never use AJAX for security sensitive applications.

Epilogue: Thanks a lot

I hope you liked the tutorial. There is a lot coming in the next parts of this tutorial:

1. An ellaborate example for creating a phonebook system powered by AJAX.
2. Using AJAX for POST applications.
3. Sending file with AJAX.

That was all from my side. Thanks for reading this tutorial. Questions, doubts and queries are welcome.
©Rohan Prabhu. mailto:rohan2kool@gmail.com


SHAdmin
Posts: 2089
Joined: Sat Dec 18, 2004 11:28 am
Contact:

Post by SHAdmin »

Thread approved and you have been credited 30 points for creating this topic.
rohan2kool
Posts: 9
Joined: Thu Apr 13, 2006 5:24 pm
Contact:

AJAX Tutorial :: Part 2

Post by rohan2kool »

Image
The AJAX tutorial: Part 2

Prologue: We meet again

I am always in a habit of writing prologues to all the tutorials/works I ever make. This is another one. We have till now, covered the very basics of AJAX. Today, we will get a bit into details. In our previous examples, we learnt how to get data from the server. But, is it enough? Just getting some data from a server file/script gives you zero points on interactivity. To make your application interactive, you need to send some data to the server and then get the results based on the data sent. This is what we'll be covering in the 2nd Part of our AJAX tutorial. We all know that example isn't the best way to teach, it is the only way. Hence, in this section I've included a complete AJAX powered phonebook demo to explain how to use AJAX for practical purposes. That's all for the prologue, let's do some AJAX.

Note: This tutorial uses the 1st part as a reference throughout the tutorial. If possible keep open another window which is displaying the 1st part of the tutorial as it will be highlyt beneficial to you.

Download the complete source code(also includes source code for 1st part) used in this tutorial: .zip format .rar format

Chapter 1. Sending data to the Server

Look at our first example. We just recieved some data from the server. The server would output the same data always. But to make our AJAX project interactive, we need to send data to the server and then create the server side script in such a way that the output is based on the data recieved.

1.1: The HTTP GET method

If you script in any server-side language, you must be knowing of the 'GET' method. It is an HTTP method to send data to the server. Data is sent in the variable-value format. Multiple variables are appended in a single string known as a 'query string'. For example, if there are two variables, 'username' and 'password' and their values are 'myname' and 'mykey', then the query string would be: 'username=myname&password=mykey'. Yes, you guessed it right(and even if you didn't), variable=value for multiple variables is joined with an ampersand sign i.e an '&' sign. This query string is then appended to the address of the file/script the query is sent to. For example, if we send this data to a script called as 'http://www.mysite.com/process.php', then the adress with the data would be: 'http://www.mysite.com/process.php?usern ... word=mykey'. It is as simple as that.

1.2: Accessing the HTTP GET variables on the server script

I don't know about other languages like asp, jsp etc., but in php, all the variables sent through the 'GET' method, are stored in a pre-defined variable array called $_GET (in versions earlier to 4.10, the array was $HTTP_GET_VARS). In our example the two pieces of dat would be stored as:

$_GET['username'] = 'myname'; //A hypothetical example. In actuality, it is just
$_GET['password'] = 'mykey'; //a pre-defined variable array defined by php.

The following script would print the data we got from the client:

Code: Select all

<?php
	if(isset($_GET['username']) && isset($_GET['password'])) {
		printf("Username: ".$_GET['username']);
		printf("
");
		printf("Password: ".$_GET['password']);
	}
?>
Save it as printget.php and check for yourself. You can also try with your own server-scripting language with the help of the respective ****. Now, how does this thing work? Let's check it out, line-by-line:

1. the isset($varname) function checks whether '$varname' as a variable has been initialized or not. In our case, if a GET query is sent with a variable called 'username', $_GET['username'] must be initialized and hence isset($_GET['username']) will return true. Same for $_GET['password']. In the first line, we check if both these variables are initiated or not.

2. If both these variables, are initiated, the script prints the username and the password to the output stream. And that's what we recieve from within the XMLHttpRequest() object.

Now look at the function fetchData() in Chapter 4.1: Doing some AJAX. Take a look at the first line:

Code: Select all

httpr.open('GET', 'returndata.php');
If you change it to this:

Code: Select all

httpr.open('GET', 'printget.php?username=myname&password=mykey');
Then you will notice a change in the string that is returned when you **** on the button. But still the question remains, what was the interactive nature of this script. Wait till we get going.

1.3: Adding interactivity

Go back to the HTML code in section 4.1. In place of this:

Code: Select all

<body>
  <p id="fields">Text here will be changed</p>
  <input type="button" value="**** to change text" ****="javascript:fetchData()" />
</body>
put this:

Code: Select all

<body>
  <p id="fields">Text here will be changed</p>
  <p>Username: <input id="username" type="text" /></p>
  <p>Password: <input id="password" type="text" /></p>
  <input id="submit" type="button" value="Send to server" ****="javascript:fetchData()" />
</body>
Basically, in this method we have created two text-fields, one is for the username and one is for the password and a button after **** which the fetchData() method is called as in the 1st part.

Also, modify the fetchData() function in JavaScript like this:

Code: Select all

function fetchData() {
	var querystring = "";
	querystring += "username=";
	querystring += username.value;
	querystring += "&password=";
	querystring += password.value;
	httpr.open('GET', 'printget.php?' + querystring);
	httpr.onreadystatechange = processData;
	httpr.send(null);
}
Please download the source code from the link in the Prologue and open the source code from 'ajax-tutorial/part2-1.3'. Complete source code has not been put here for space considerations.

Now this is what we'll observe:

http://tritium.frihost.net/tutorials/aj ... image1.gif

I've shown two 'After ****' images with two different cases of usernames/passwords. Now, how does this work. Let's get to it, line-by-line:

1. We create a string called 'querystring' which shall be the data we send to the server. We initialize it as a string. Though it is not necessary, it is always a good habit to do so.

2. We add the string 'username=' to the existing string.

3. We add the text in the username field to the querystring. Supposing the username entered was 'rohan', the querystring would now be: 'username=rohan'.

4. Now, we add the ampersand symbol to it and also the variable name of the next variable. Know the query string is: 'username=rohan&password='.

5. Finally, we add the text in the password field to the querystring. The querystring now: 'username=rohan&password=mykey'.

6. We did in so many steps just to make things clear to you. Actually, you could have done that in a much smaller way too:

Code: Select all

httpr.open('GET', 'printget.php?username=' + username.value + '&password=' + password.value);
This is what it appears to the JavaScript interpretor:

Code: Select all

httpr.open('GET', 'printget.php?username=rohan&password=mykey');
As it is appended in the address itself, it is easily interpreted by the server and the variables are passed on to the CGI-app (which in our case is php) and it creates the usable variables for us. GET is a simple way to send data, but not very secure. In the next chapter, I'll explain the usage of the much more secure POST method which is also much more reliable for large data strings.

You can also add authentication to this by modifying the php script a bit:

Code: Select all

<?php
	if(isset($_GET['username']) && isset($_GET['password'])) {
		if($_GET['username'] == "rohan" && $_GET['password'] == "mykey") {
			printf("Login succeeded");
		} else {
			printf("Wrong username/password");
		}
	}
?>
Try this code yourself. I've not provided with a screenshot or any other post-test material.

1.4: The POST method

The POST method is much more secure and reliable than the GET method. The querystring remains the same, with the only two differences: 1. GET is sent as normal text(ASCII), while you have to send POST strings with a MIME-type which specifies the type of content you are sending. 2. POST is not appended to the file address. and on the server side, with php, the pre-defined array-variable is $_POST rather than $_GET

The reasons you should use POST are:

1. Security.
2. POST supports longer strings than GET.
3. You can send even files with POSt. However, it is not possible to send files via XMLHttpRequest (as of now).

Using POST is as simple as using GET. Let us look at the modified fetchData() example:

Code: Select all

function fetchData() {
	var querystring = "";
	querystring += "username=";
	querystring += username.value;
	querystring += "&password=";
	querystring += password.value;

	httpr.open('POST', 'printpost.php');
	httpr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	httpr.onreadystatechange = processData;
	httpr.send(querystring);
}
The changes are:

1. We have set a request header using the setRequestHeader() method of XMLHttpRequest. As I said, this is necessary as the POST method requires the MIME encoding for the data to be sent. I won't get much into MIME. Basically, it is a method of telling the type of a file for internet purposes. For now, our Content-type would be 'application/x-www-form-urlencoded' Get more on MIME here: http://en.wikipedia.org/wiki/MIME . The header is actually an HTTP header which is extremely a crucial part of the server-client interchange on all server development platforms. HTTP headers basically, is a data transfer among the data-sender to the data-reciever on what to do with the data sent to it, what is the type of data sent to it and the rest.

2. Then, we have changed the params of the send() method from 'null' to the query string. There was a reason why we did this. In the GET method, the query is appended to the URL, but in the POST method it is not so. It is sent more like a file transfer among two networked computers. Hence, we send the data using the 'send()' method with the query string as the parameter.

Also, you need to modify your printget.php file a bit. Change it's name to printpost.php and here is the code to it:

Code: Select all

<?php
	if(isset($_POST['username']) && isset($_POST['password'])) {
		printf("Username: ".$_POST['username']);
		printf("
");
		printf("Password: ".$_POST['password']);
	}
?>
Now try this out. It will work same as the previous example, but with an internal difference that it uses a different method altogether.

1.5: The thing till now

You might now have understood how you can add interactivity to your AJAX pages. You can then further play with the values you recieve and then create complex systems. It is not easy to do this directly into the practical world, but don't worry, I'll guide you through that also. In the next section, I'll present you a phonebook example which is completely AJAX powered for viewing and editing the phonebook. It is followed my some excercises so you can sharpen your AJAX skills. All this and more, in the sections to come... keep reading.

Chapter 2: The phonebook example

2.1: Introduction

This is not going to be a very big example. We are going to create a small phonebook, in which anyone can add and view the contacts. I am not going into stuff like authentication and all that. That requires another tutorial by itself. We are going to use MySQL to store the names, email addresses, phone numbers and postal addresses. So.. let's get down to some PRACTICAL AJAX...

2.2: The MySQL database

Firstly, we need to create a MySQL table which will store the data of the various people we enter in the phonebook. I've created a basic MySQL table for that and added some dummy entries in it. Here is the table:

Code: Select all

+----+------------+-----------+--------------+--------------------------+--------------------------------------+
| id | first_name | last_name | phone        | email                    | postal_add                           |
+----+------------+-----------+--------------+--------------------------+--------------------------------------+
|  1 | Rohan      | Prabhu    | 0265-2236082 | rohan2kool@gmail.com     | Some home, some street, some city    |
|  2 | Some       | Person    | 2342356754   | somepersron@somemail.com | 343-45, This street, Delhi           |
|  3 | Nikhil     | Verma     | 32452356     | nikhil@nikhil.com        | 3456, A good place, Lucknow          |
|  4 | Pallab     | Sonai     | 435436546    | pallab@pallab.com        | The big palace, Asansol, West-Bengal |
+----+------------+-----------+--------------+--------------------------+--------------------------------------+
Create this table in a database called 'general'. If you have downloaded the source code for this tutorial, the SQL file is already provided in it to create a table like this in a database called 'general'. It is located in 'ajaxtutorials/part2-2.2/table.sql'.

2.3: The interface

The interface is going to be very simple. There is going to be a searchbox and you can type in the name of the person whose contact info you want to view. There won't be any button... The results will be displayed as you type!!! This is the power of what AJAX can do. However, this should not be practised. I've given it in this tutorial only and only to explain what AJAX is most used for. Actually, it is better if you create a button and then query the server when the button is **** because that will save you bandwidth and will help avert many problems. This is the basic interface:

http://tritium.frihost.net/tutorials/aj ... e-init.gif

We won't be going much into this. The source code is available in the download. We won't be going much into this because it is only HTML and CSS and that is not what this tutorial is about.

2.3.1: The hidden interfaces

There will be two hidden interfaces which we shall be using, first one for the editing box and second one for displaying the phonebook entry. As we just won't be moving from the page once it is loaded, we will create these interfaces in the very first page and hide them. This is the page with the hidden interfaces:

http://tritium.frihost.net/tutorials/aj ... hidden.gif

We will be hiding those two interfaces with a simple CSS piece of code. In the top level <div>, or the complete HTML slab which we want to hide, we will add an HTML attrib. The code(the body section onle) as of now is:

Code: Select all

<body>
  <div id="header"><span>MyAJAX phonebook</span></div>
  <div id="topbar"><p> <a href="javascript:createNew()">Create new contact</a> | <i>Search for a contact:</i> Name: <input id="sname" type="text" /></p></div>
  
  <div id="newentry"><div id="ne_head"><span>Add a new contact</span></div>
  <div id="ne_content">
<span><b> First Name: </b><input id="name" type="text" /> | <b>Last Name: </b><input id="name" type="text" />


  <b> email address: </b><input id="mail" type="text" />


  <b> Phone no.: </b><input id="phone" type="text" />


  <b> Postal Address: </b><input id="address" type="text" />


  <input type="button" value="Add contact" id="addcontact" style="margin: 2px"/></div></div>

  <div id="viewentry"><div id="ve_head"><span>fName lName</span></div>
  <div id="ve_content">
<span><b>email adress: </span></b><span id="mview">email@email.com</span>


  <b>Phone number: </b><span id="phone">2236082</span>


  <b>Postal Address: </b><span id="address">My house</span>

</div></div>
 </body>
For the the two <div>s with the id as "newentry" and "viewentry", we will add an HTML attrib in this way:

Code: Select all

 <body>
  .....
  <div id="newentry" style="display: none">
  ....</div>
  ......
  <div id="viewentry" style="display: none">
  ....</div>
  ...
 </body>
The altered code is present in the folder 'ajaxtutorials/phonebook-complete/main.php'. The HTML attrib 'style' is used to add CSS rules to the element. The 'display: none' CSS rule will hide the interface. We will call it using JavaScript whenever needed.

2.4: Creating the php scripts

From now on, all the sources will be available in the folder 'ajaxtutorials/phonebook-complete'. We need to create 3 php scripts: One to search for the contacts, one to return the data of a given contact and one to add new contact.

2.4.1: Creating the 'searchcontacts.php' php script

This is the code for the searchcontacts.php script:

Code: Select all

<?php
	if(isset($_GET['sfor']) && if($_GET['sfor'] != '') {
		$conn = @ mysql_connect("localhost", "root", "")
			or die("-1");

		$rs = @ mysql_select_db("general", $conn)
			or die("-1");

		$rs = @ mysql_query("select * from phonebook")
			or die("-1");

		$matches = array();

		while($row = mysql_fetch_array($rs)) {
			$fname = $row['first_name'];
			$lname = $row['last_name'];
			$id = $row['id'];

			if(stristr($fname, $_GET['sfor']) || stristr($lname, $_GET['sfor'])) {
				$matches[] = $id.'|'.$fname.$lname;
			}
		}
	}

	foreach($matches as $value) {
		printf($value);
		printf('|');
	}
?>
Firstly this script checks if a GET varialbe called 'sfor' is initialized. If it is, then it connects to the MySQL database and selects a database called 'general'. Then all the contents of a table called 'phonebook' in that database are called with the MySQL query 'select * from database'. An array called matches is declared which will contain all the results relevant to the search. Finally, using a while loop, we examine all the values of the result resource which we got from running the MySQL query and check if in any cases the first name OR the last name returns a part of the search query. In this case even 'ha' would return 'Rohan' as a relevant result. If that is feeling wrong to you, you can change the stristr() function to: $fname == $_GET['sfor']. But you then need to type the complete name or parse it into first name and last name at some point(in php, or JavaScript).

For this example, this will server our purpose. Then if any search result is found relevant, it is added to the array called 'matches'. Finally, using a foreach() loop, all the variables are outputted seperated by a '|'. At the same, time we append the 'id' of these contacts too, which will be useful for use for further querying. For example, if the results are 'Rohan Prabhu' and 'Nikhil Verma' and 'Some Person' for the search string 'n', the output will be: '1|Rohan Prabhu|2|Nikhil Verma|3|Some Person'.

You can try out this script by using your browser. For example, you want to test it to search for 'rohan', you can search it this way:

http://localhost/tutorials/ajax/phonebook-complete/searchcontacts.php?sfor=rohan

For the search script, that's it.

2.4.2: The 'returndata.php' PHP script

The code for the 'returndata.php' PHP script is:

Code: Select all

<?php
	if(isset($_GET['qid']) && $_GET['qid'] != '') {
		$conn = @ mysql_connect("localhost", "root", "")
			or die("-1");

		$rs = @ mysql_select_db("general")
			or die("-1");

		$rs = @ mysql_query("select * from phonebook where id=".$_GET['qid'])
			or die("-1");

		while($row = mysql_fetch_array($rs)) {
			$fname = $row['first_name'];
			$lname = $row['last_name'];
			$phone = $row['phone'];
			$email = $row['email'];
			$postal = $row['postal_add'];

			printf($fname.' '.$lname.'|'.$phone.'|'.$email.'|'.$postal);
		}
	}
?>
Most of things here are same as in 'searchcontacts.php' script. The key difference is the query. Here, we have supplied a 'where' condition. The query 'select * from phonebook where id='.$qid. Here '$qid' is the id of the contact whose info we want. In our phonebook, we will get this 'id' from 'searchcontacts.php'. This MySQL query tells the database to give all the results from the table 'phonebook', where the id of the fields is equal to $qid. We need not provide the database with the id while adding new contacts as the 'id' field is 'auto_increment' i.e it is incremented automatically when a new field is added. Check table.sql for more info.

You can check this script also by appending the GET variable 'qid=' followed by the 'id' to your address, similarly as you tested 'searchcontacts.php'.

2.4.3: The 'addcontact.php' PHP script

The code for the 'addcontact.php' PHP script:

Code: Select all

<?php
	if(isset($_POST['fname']) && isset($_POST['lname']) && isset($_POST['email']) && isset($_POST['phone']) && isset($_POST['postal'])) {
		if($_POST['fname'] == "" || $_POST['lname'] == "") {
			printf("Please enter the First Name or the Last Name of the contact");
			die();
		} else {
			$fname = $_POST['fname'];
			$lname = $_POST['lname'];
			$email = $_POST['email'];
			$phone = $_POST['phone'];
			$postal = $_POST['postal'];

			$conn = @ mysql_connect("localhost", "root", "")
				or die("There was an error adding your contact");

			$rs = @ mysql_select_db("general")
				or die("There was an error adding your contact");

			$sql = "insert into phonebook(first_name, last_name, email, phone, postal_add) values(\"".$fname."\", \"".$lname."\", \"".$email."\", \"".$phone."\", \"".$postal."\")";

			$rs = mysql_query($sql, $conn)
				or die("There was an error adding your contact");

			die("New contact succesfully added");
		}
	}
?>
In this example, first it is checked whether the variables are properly initialized or not and then it is checked whether either of the First Name and the Last Name is filled or not. If not, the program terminates, but if filled, it first assigns some php variables from the POST variable and then connects to the MySQL database, selects a database and then creates a MySQL query which instructs to add a field to the table called 'phonebook'.

This script as others can't be tested by appending the params to the address as this script checks for POST variables. I did this just for example not for any specific reason.

Note: You must have noticed, that in case of errors, we are ending the execution of the php script and outputting "-1". We are doing this so as to catch errors. If the XMLHttpRequest gets a "-1" as the return, we can understand that an error has been caused.

Chapter 3: Making the phonebook

Now we have created the server side modules for the phonebook. Now we will bundle it all on the client site to make an AJAX powered phonebook. This work will be split in 3 parts: 1. To send the search string and then get all the relevant things and to display them as links. 2. After the user **** on one of them, contact the server and get more info for the contact. 3. Creating a new contact

3.1: The search script

Firstly, we'll create the search script. The idea is, whenver the user types anything in the search textbox, the query is sent to the server. Even if after the query is sent and a key is pressed within the search textbox, the older connection to the server is aborted if not completed already and a new connection will be esablished. Firstly, we need to tell it to execute a function every time a key is pressed when in the searchbox. View the code for the search box:

Code: Select all

<input id="sname" type="text" />
We will change it to:

Code: Select all

<input id="sname" type="text" onkeyup="javascript:execSearch()" />
It will cause it to execute a function called 'execSearch()' whenver a key is pressed in the search textbox. Now, let's look at the execSearch() function:

Code: Select all

var pendingQuery = 0;

function execSearch() {
  if(pendingQuery) {
	  httpr.abort();
  } 
  
  var query = sname.value;
  httpr.open('GET', 'searchcontacts.php?sfor=' + query);
  httpr.onreadystatechange = handleReturns;
  httpr.send(null);
  pendingQuery = 1;
}
As you know most of it by now, we won't do it on a line-by-line basis. Basically, a variable called 'pendingQuery' is constantly updated to tell whether a query is running or not. If it is true(i.e 1), the XMLHttpRequest is aborted. In any case, a new request is made and is sent. Once the new request is made, the variable 'pendingQuery' is set to 'true' i.e '1' to tell that a query is in progress. The new request, when recieved calls the handleReturns function. We will check that function now:

Code: Select all

function handleReturns() {
	if(httpr.readyState == 4) {
		if(httpr.status == 200) {
			var data = httpr.responseText;		  
			  
			if(data == "-1") {
				alert("Unknown error has occured while getting data from the server. Reload the page to try again");
				pendingQuery = 1;
			} else {
				var contacts = new Array();
				var contids = new Array();
				var ids = new Array();
				contids = data.split('|');

				var idindex = 0;
				var cindex = 0;

				for(var i=0; i<=contids.length; i++) {
					if(contids[i] == '') {
						continue;
					}
				
					if(i%2 != 0) {
						contacts[cindex] = contids[i];
						cindex++;
					} else {
						ids[idindex] = contids[i];
						idindex++;
					}
				}

				var inserts = "";

				for(var i=0; i<=(ids.length-1); i++) {
					inserts += '<a href="javascript:getInfo(\'' + ids[i] + '\')">' + contacts[i] + '</a>' + '\n';
				}

				****.getElementById('display').innerHTML = "
<i>Search Results: </i>" + inserts;
			}
		}
	}
}
Note: This is just a code snippet which shall not work individually.

Phew.. that's a big function. For the first parts, let's leave that out. Let's move on to the part which says: if(data == "-1") . Here, we first check if any errors are occured. Remember, our php scripts output -1, if there are any errors. If there isn't any error, we first create an array called 'contacts' and an array called 'ids'. Both of these are parallel arrays i.e if one contains the name of the person, other will contain the id relating to the name. Now, focus your attention towards the way we output our strings. It is seperated by a '|'. Then, the complete data returned is split and is assigned to an array called 'contids'. What happens actually: Supposing the output is: 1|Rohan|2|Nikhil|, then contids[0] will be '1', contids[1] will be 'Rohan', contids[2] will be '2', contids [3] will be 'Nikhil' and so on.

Then, it is distributed in a way such that the array ids[] contains the even elements and the array contacts[] the odd elements. Also, notice that we say if(contids == '') {continue;}. This is important as php sometimes outputs some whitespaces which are actually a truncated null character(in fact whitespaces are returns, tabs, spaces etc.) i.e ''. We need to remove those characters as they hinder the process of parsing. When a '' whitespace is encountered we 'continue' the loop. What 'continue' does is to quit the current loop at the very instant it is called but continue from the next iteration of the loop. So, a '' whitespace is never considered to be a part of any array, ids or contacts.

The variables, cindex and idindex are incremented whenever a variable is filled in that array, so as to fill the succesive elements of the array. These are all JavaScript techniques and we won't go into much detail there.

Next, we cycle through all the elements of the contacts[] and ids[] array to create a hyperlink, which when **** shall execute a JavaScript function called getInfo(contact_id), which gives more info of the contact with the id as 'contact_id'. For example, if the name is 'Nikhil Verma' and the id is '2', the link formed will be:

<a href="javascript:getInfo('2')">Nikhil Verma</a>

All the search results are thus created into links and finally, they are shown in the page, by inserting them in a <span> field which has the id="display". This is how things look:

Image

3.2: Viewing the complete contact info

We need two JavaScript functions for this:

Code: Select all

var getc = createRequestObject();

function getInfo(contact_id) {
	getc = createRequestObject();
	getc.open('GET', 'returndata.php?qid=' + contact_id);
	getc.onreadystatechange = fillInfo;
	getc.send(null);
}

function fillInfo() {
	if(getc.readyState == 4) {
		if(getc.status == 200) {
			var data = getc.responseText;
			var data = data.split('|');

			****.getElementById('dis_name').innerHTML = data[0];
			****.getElementById('dis_phone').innerHTML = data[1];
			****.getElementById('dis_mail').innerHTML = data[2];
			****.getElementById('dis_add').innerHTML = data[3];

			****.getElementById('viewentry').style.display = "";
		}
	}
}
What we do here, is create another XMLHttpRequest object so that the user can also make searches side-by-side while the pages are loading. Remember, we created the hyperlinks in such a way that they call a javascript function called 'getInfo(contact_id)', in which contact_id is the 'id'. This function then calls the page 'returndata.php?qid=' + contact_id. In this case, the 'qid' is the id of the contact we are requestin information about.

fillInfo() is the javascript function which is executed when the request is completed. Here, we get the response text and you must be remembering the output of this script(section 2.4.2). This is also seperated by '|' in such a way that the first part is the name, 2nd part is the phone no., 3rd part is the email and 4th part is the postal address. This data is then added to specific fields inside the hidden interfaces. Please note that the data in the 'hidden interfaces' chapter varies from the page used in this part. Please refer to the source code in the folder 'ajaxtutorial/phonebook-complete' and not in the hidden interfaces part. Finally, we tell the page to display the <div> layer which is holding the information. That's all. Now, you can search side-by-side, **** like frenzy for your contact infos and do whatever you want. This is how it looks:

Image

3.3: Adding new contacts

Firstly, please check the complete HTML code from your source download. It is very important that you notice how the functions are called. When you **** on 'Create New Contact', a function called createNew() is called which displays a box in which you have to enter the details for a new contact. The box has a button called 'Add contact' **** on which calls a function 'newContact()'. Here is the code"

Code: Select all

function newContact() {
	****.getElementById('newentry').style.display = "none";

	var fname = ****.getElementById('add_fname').value;
	var lname = ****.getElementById('add_lname').value;
	var phone = ****.getElementById('add_phone').value;
	var email = ****.getElementById('add_mail').value;
	var add = ****.getElementById('add_address').value;

	newc = createRequestObject();
	newc.open('POST', 'addcontact.php');
	newc.onreadystatechange = verifyNew;
	newc.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	newc.send('fname=' + fname + '&lname='+ lname + '&email=' + email +'&phone=' + phone + '&postal=' + add);
}

function createNew() {
	****.getElementById('newentry').style.display = "";
}

function verifyNew() {
	if(newc.readyState == 4) {
		if(newc.status == 200) {
			var data = newc.responseText;
			alert(newc.responseText);
		}
	}
}
When the createNew() function is called, it displays the box to you, by changing the CSS attrib. You can now fill out the form and when you **** on the button 'Add contact', a function called 'newContact()' is called. This function at the first go hides the box and after taking the data from the box, it clears the contents so that more users can be added easily, and then contacts the server with the POST variables. Please checkout section 2.1.4 for understanding how to send POST data through AJAX.

The server handles out the request and returns the status which is then conveyed by the JS to the user within the verifyNew() function. Here is a screenshot of that in action:

Image

Appendix: Problems of caching

It is a problem in some browsers that the php returns are cached and repeated requests to the script will give you the same result, despite your expectations for other content as the browser might have cached it and it is returning the same data again. To overcome this, put this piece of code on top of every php script from where you intend to recieve data via AJAX:

Code: Select all

<?php
	header("Cache-control: no-cache");
	header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
?>
Appendix 2: The complete client-side code

Here is the complete code for the main.php file with the CSS, JavaScript and the complete XML in the phonebook example:

Code: Select all

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
  <title>MyAJAX phonebook<title>
  <script type="text/javascript">
  <!--
  var httpr = createRequestObject();
  var getc;
  var newc;

  function createRequestObject() {
    var xhr;
    var browser = navigator.appName;
    if(!window.XMLHttpRequest){
        if(browser == "Microsoft Internet Explorer") {
            xhr = new ActiveXObject("Microsoft.XMLHTTP");
        } else {
            alert("Sorry, this page cannot be viewed on your browser i.e " + browser);
            return false;
        }
    } else {
        xhr = new XMLHttpRequest();
    }
    return xhr;
  }

  function getInfo(contact_id) {
	  getc = createRequestObject();
	  getc.open('GET', 'returndata.php?qid=' + contact_id);
	  getc.onreadystatechange = fillInfo;
	  getc.send(null);
  }

  function newContact() {
	  ****.getElementById('newentry').style.display = "none";
	  var fname = ****.getElementById('add_fname').value;
	  var lname = ****.getElementById('add_lname').value;
	  var phone = ****.getElementById('add_phone').value;
	  var email = ****.getElementById('add_mail').value;
	  var add = ****.getElementById('add_address').value;

	  ****.getElementById('add_address').value = "";
	  ****.getElementById('add_mail').value = "";
	  ****.getElementById('add_phone').value = "";
	  ****.getElementById('add_fname').value = "";
	  ****.getElementById('add_lname').value = "";

	  newc = createRequestObject();
	  newc.open('POST', 'addcontact.php');
	  newc.onreadystatechange = verifyNew;
	  newc.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
	  newc.send('fname=' + fname + '&lname='+ lname + '&email=' + email +'&phone=' + phone + '&postal=' + add);
  }

  function createNew() {
	  ****.getElementById('newentry').style.display = "";
  }

  function verifyNew() {
	  if(newc.readyState == 4) {
		  if(newc.status == 200) {
			  var data = newc.responseText;

			  alert(newc.responseText);
		  }
	  }
  }

  function fillInfo() {
	  if(getc.readyState == 4) {
		  if(getc.status == 200) {
			  var data = getc.responseText;
			  var data = data.split('|');

			  var starting = 0;

			  ****.getElementById('dis_name').innerHTML = data[0];
			  ****.getElementById('dis_phone').innerHTML = data[1];
			  ****.getElementById('dis_mail').innerHTML = data[2];
			  ****.getElementById('dis_add').innerHTML = data[3];

			  ****.getElementById('viewentry').style.display = "";
		  }
	  }
  }

  var pendingQuery = 0;

  function execSearch() {
	  if(pendingQuery) {
		  httpr.abort();
	  } 
	  
	  var query = ****.getElementById('sname').value;
	  httpr.open('GET', 'searchcontacts.php?sfor=' + query);
	  httpr.onreadystatechange = handleReturns;
	  httpr.send(null);
	  pendingQuery = 1;
	
  }

  function handleReturns() {
	  if(httpr.readyState == 4) {
		  if(httpr.status == 200) {
			  var data = httpr.responseText;		  
			  
			  if(data == "-1") {
				  alert("Unknown error has occured while getting data from the server. Reload the page to try again");
				  pendingQuery = 1;
			  } else {
				  var contacts = new Array();
				  var contids = new Array();
				  var ids = new Array();
				  contids = data.split('|');

				  var idindex = 0;
				  var cindex = 0;

				  for(var i=0; i<=contids.length; i++) {
					  if(contids[i] == '') {
						  continue;
					  }
					  if(i%2 != 0) {
						  contacts[cindex] = contids[i];
						  cindex++;
					  } else {
						  ids[idindex] = contids[i];
						  idindex++;
					  }
				  }

				  var inserts = "";

				  for(var i=0; i<=(ids.length-1); i++) {
					  inserts += '<a href="javascript:getInfo(\'' + ids[i] + '\')">' + contacts[i] + '</a>' + '\n';
				  }

				  ****.getElementById('display').innerHTML = "
<i>Search Results: </i>" + inserts;
			  }
		  }
	  }
  }
  //-->
  </script>
  <style type="text/css">
  <!--
	body {
		font-family: Verdana, Arial, Helvetica, sans-serif;
		font-size: 10px;
	}

	#header {
		border: 1px solid #000000;
		font-family: Verdana, Arial, Helvetica, sans-serif;
		background: #FFC832;
		font-size: 25px;
		border-bottom: none;
	}

	#topbar {
		border: 1px solid #000000;
		font-family: Verdana, Arial, Helvetica, sans-serif;
		background: #FCFCC8;
		font-size: 10px;
	}

	input {
		border: 1px solid #000000;
		font-family: Verdana, Arial, Helvetica, sans-serif;
		font-size: 10px;
	}

	#newentry {
		margin-top: 5px;
		border: 1px solid #000000;
	}

	#ne_head {
		background: #0080FF;
		border-bottom: 1px solid #000000;
		font-size: 18px;
		color: #FFFFFF;
	}

	#viewentry {
		margin-top: 5px;
		border: 1px solid #000000;
	}

	#ve_head {
		background: #0080FF;
		border-bottom: 1px solid #000000;
		font-size: 28px;
		color: #FFFFFF;
	}

	#ve_content {
		font-size: 14px;
	}
  -->
  </style>
 </head>

 <body>
  <div id="header"><span>MyAJAX phonebook</span></div>
  <div id="topbar"><p> <a href="javascript:createNew()">Create new contact</a> | <i>Search for a contact:</i> Name: <input id="sname" type="text" onkeyup="javascript:execSearch()" />
<span id="display"></span></p></div>
  
  <div id="newentry" style="display: none"><div id="ne_head"><span>Add a new contact</span></div>
  <div id="ne_content">
<span><b> First Name: </b><input id="add_fname" type="text" /> | <b>Last Name: </b><input id="add_lname" type="text" />


  <b> email address: </b><input id="add_mail" type="text" />


  <b> Phone no.: </b><input id="add_phone" type="text" />


  <b> Postal Address: </b><input id="add_address" type="text" />


  <input ****="javascript:newContact()" type="button" value="Add contact" id="addcontact" style="margin: 2px"/></div></div>

  <div id="viewentry" style="display: none"><div id="ve_head"><span id="dis_name">fName lName</span></div>
  <div id="ve_content">
<span><b>email adress: </span></b><span id="dis_mail">email@email.com</span>


  <b>Phone number: </b><span id="dis_phone">2236082</span>


  <b>Postal Address: </b><span id="dis_add">My house</span>

</div></div>
 </body>
</html>
Conclusion

That brings us to the end of the 2nd part of my AJAX tutorial. I hope you enjoyed it and could learn from it. It had been a wonderfull experience from my side too, because it is the first time I am being so descriptive and writing such a long tutorial. Any doubts, queries and suggestions are welcome. It took me 11.30 hours in total to finish both the parts, so please please reply to the post to make this tutorial worth the effort. That's all for now. Meet you soon in some other tutorial, where I promise to introduce you further in the wonderfull world of programmming.

©Rohan Prabhu. All images, text and content is (copyright) ©Rohan Prabhu. Source code provided here maybe re-distributed, but should accompany the copyright notice. Duplication of the article without prior permission is not allowed. mailto: rohan2kool@gmail.com
delivi
Posts: 454
Joined: Sun Mar 26, 2006 1:24 pm
Contact:

Post by delivi »

this is a great tutorial, but I've already seen the same tutorial in the Tutorials section of ThinkDigit.com forum, and also featured in the digit magazine.
rohan2kool
Posts: 9
Joined: Thu Apr 13, 2006 5:24 pm
Contact:

Post by rohan2kool »

delivi wrote:this is a great tutorial, but I've already seen the same tutorial in the Tutorials section of ThinkDigit.com forum, and also featured in the digit magazine.
i bet you have.. it was posted in the thinkdigit forum by me only.. and the digit staff picked it up from there. Glad you liked it.
delivi
Posts: 454
Joined: Sun Mar 26, 2006 1:24 pm
Contact:

Post by delivi »

that is great to have you here friend.

i'm sorry i thought somebody steled your tutorial, this is really a great tutorial, thanx for sharing. Its great to meet you in smokyhosts, please do regularly share your knowledge with us.
SHAdmin
Posts: 2089
Joined: Sat Dec 18, 2004 11:28 am
Contact:

Post by SHAdmin »

'rohan2kool', you have been credited with another 40 points to continue to work on this very usefull "How To".
Post Reply