Writing Ajax


This chapter starts by taking an in-depth look at the example you first saw in Chapter 2-index.html, which is shown in Figure 3.1.

image from book
Figure 3.1: A first Ajax application

This application boasts a button with the caption “Fetch the Message.” When you click that button, the application uses Ajax techniques to download text behind the scenes and display it.

The data that the application reads from the server is stored in a file named data.txt. Here are the contents of that file:

 This text was fetched from the server with Ajax.

When you click the button in this application, the application downloads this text and displays it, all without a page refresh. You can see the results in Figure 3.2.

image from book
Figure 3.2: Downloading text using Ajax techniques

Here’s what the code for this application looks like:

 <html>   <head>     <title>An Ajax demo</title>     <script language = "javascript">       var XMLHttpRequestObject = false;       if (window.XMLHttpRequest) {         XMLHttpRequestObject = new XMLHttpRequest();       } else if (window.ActiveXObject) {         XMLHttpRequestObject = new           ActiveXObject("Microsoft.XMLHTTP");       }       function getData(dataSource, divID)       {         if(XMLHttpRequestObject) {           var obj = document.getElementById(divID);           XMLHttpRequestObject.open("GET", dataSource);           XMLHttpRequestObject.onreadystatechange = function()           {             if (XMLHttpRequestObject.readyState == 4 &&               XMLHttpRequestObject.status == 200) {                 obj.innerHTML =                   XMLHttpRequestObject.responseText;             }           }           XMLHttpRequestObject.send(null);         }       }     </script>   </head>   <body>     <H1>An Ajax demo</H1>     <form>       <input type = "button" value = "Fetch the message"         onclick = "getData('data.txt', 'targetDiv')">     </form>     <div >       <p>The fetched message will appear here.</p>     </div>   </body> </html> 

We’re going to take this code apart now.

Setting up the application

This application starts by displaying the text “The fetched message will appear here” in a <div> element that has the ID targetDiv. This is where much of the action is going to take place: the code in this application is going to display the text fetched from the server in this <div> element. Here’s what the <div> looks like:

 <body>   <H1>An Ajax demo</H1>   <form>     <input type = "button" value = "Fetch the message"       onclick = "getData('data.txt', 'targetDiv')">   </form>   <div >     <p>The fetched message will appear here.</p>   </div> </body>

There’s also a button whose onclick event attribute is connected to a JavaScript function named getData:

 <body>   <H1>An Ajax demo</H1>   <form>     <input type = "button" value = "Fetch the message"       onclick = "getData('data.txt', 'targetDiv')">   </form>   <div >     <p>The fetched message will appear here.</p>   </div> </body>

Note that the getData function is passed two text strings: the name of the file to fetch from the server, and the ID of the <div> element to display the results in. So what’s the next step? Seeing how the JavaScript in this application does its thing.

Writing the JavaScript

The getData function is in the <script> element in the application’s Web page, and it’s set up to accept two arguments: dataSource (the name of the file on the server to fetch) and divID (the ID of the <div> element to display the results in):

 <script language = "javascript">   function getData(dataSource, divID)   {     .     .     .   } </script>

Then the code checks on a variable named XMLHttpRequestObject to see whether it contains an object or null:

 <script language = "javascript">   function getData(dataSource, divID)   {     if(XMLHttpRequestObject) {     .     .     .   } </script>

So what is this XMLHttpRequestObject object? This is the object that forms the basis of Ajax. The XMLHttpRequest object is built into modern browsers, and it lets you communicate with the server behind the scenes.

This example application creates an XMLHttpRequest object as soon as its Web page is loaded into the server so that that object will be available in code. All of which means it’s time to create your own XMLHtppRequest object.

Creating an XMLHttpRequest object

When this application loads, it creates a variable named XMLHttpRequestObject and sets it to false like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;   function getData(dataSource, divID)   {     if(XMLHttpRequestObject) {     .     .     .   } </script>

This variable is set to false initially so that if the attempt to create an XMLHttpRequest object fails, this variable will still hold false, which is something you can test easily in code.

Note 

You can also set JavaScript variables to a value of true. True and false are called Boolean values in JavaScript.

Netscape Navigator (version 7.0 and later), Apple Safari (version 1.2 and later), and Firefox let you create XMLHttpRequest objects directly with code like this:

 XMLHttpRequestObject = new XMLHttpRequest();

So how do you know whether you’re dealing with a browser that can create an XMLHttpRequest object this way? The XMLHttpRequest object is part of the browser’s window object; you can test whether it exists using this if statement:

 <script language = "javascript">   var XMLHttpRequestObject = false;      if (window.XMLHttpRequest) {     .     .     .   }     .     .     . </script>

If the browser can create XMLHttpRequest objects this way, you can create such an object and store it in the XMLHttpRequestObject variable like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;     if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   }     .     .     . </script>

That takes care of Navigator, Safari, and Firefox. What if you’re dealing with the Microsoft Internet Explorer? In that case, window.XMLHttpRequest is undefined; window.ActiveXObject is what you want to check instead, like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;   if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } else if (window.ActiveXObject) {     .     .     .   }     .     .     . </script>

If window.ActiveXObject exists, you can create an XMLHttpRequest object in Internet Explorer (version 5.0 and later) this way (you’ll see additional ways to create XMLHttpRequest objects in Internet Explorer in the next chapter):

 <script language = "javascript">   var XMLHttpRequestObject = false;   if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } else if (window.ActiveXObject) {     XMLHttpRequestObject = new       ActiveXObject("Microsoft.XMLHTTP");   }     .     .     . </script>

Now you’ve got an XMLHttpRequest object-great. The basic properties and methods of this object are the same across all browsers that support it, but there are significant differences among the more advanced features of this object in different browsers. So what properties and methods are available in these objects in different browsers? The properties of the Internet Explorer XMLHttpRequest object are listed in Table 3.1, and its methods are listed in Table 3.2. The properties of this object for Mozilla, Netscape Navigator, and Firefox are listed in Table 3.3, and the methods are listed in Table 3.4. As yet, Apple hasn’t published a full version of the properties and methods for its XMLHttpRequest object, but it has published a set of commonly used properties, which appear in Table 3.5, and commonly used methods, which appear in Table 3.6.

Table 3.1: XMLHttpRequest Object Properties for Internet Explorer
Open table as spreadsheet

Object Property

Description

onreadystatechange

Contains the name of the event handler that should be called when the value of the readyState property changes. Read/write.

readyState

Contains state of the request. Read-only.

responseBody

Contains a response body, which is one way HTTP responses can be returned. Read-only.

responseStream

Contains a response stream, a binary stream to the server. Read-only.

responseText

Contains the response body as a string. Read-only.

responseXML

Contains the response body as XML. Read-only.

Status

Contains the HTTP status code returned by a request. Read-only.

statusText

Contains the HTTP response status text. Read-only.

Table 3.2: XMLHttpRequest Object Methods for Internet Explorer
Open table as spreadsheet

Object Method

Description

abort

Aborts the HTTP request.

getAllResponseHeaders

Returns all the HTTP headers.

getResponseHeader

Returns the value of an HTTP header.

Open

Opens a request to the server.

Send

Sends an HTTP request to the server.

setRequestHeader

Sets the name and value of an HTTP header.

Table 3.3: XMLHttpRequest Object Properties for Mozilla, Firefox, and Netscape Navigator
Open table as spreadsheet

Object Property

Description

channel

Contains the channel used to perform the request. Read-only.

readyState

Contains state of the request. Read-only.

responseText

Contains the response body as a string. Read-only.

responseXML

Contains the response body as XML. Read-only.

status

Contains the HTTP status code returned by a request. Read-only.

statusText

Contains the HTTP response status text. Read-only.

onreadystatechange

Contains the name of the event handler that should be called when the value of the readyState property changes. Read/write.

Table 3.4: XMLHttpRequest Object Methods for Mozilla, Firefox, and Netscape Navigator
Open table as spreadsheet

Object Method

Description

abort

Aborts the HTTP request.

getAllResponseHeaders

Returns all the HTTP headers.

getResponseHeader

Returns the value of an HTTP header.

openRequest

Native (non-script) method to open a request.

overrideMimeType

Overrides the MIME type the server returns.

Open

Opens a request to the server.

Send

Sends an HTTP request to the server.

Table 3.5: XMLHttpRequest Object Properties for Apple Safari
Open table as spreadsheet

Object Property

Description

onreadystatechange

Contains the name of the event handler that should be called when the value of the readyState property changes. Read/write.

readyState

Contains state of the request. Read-only.

responseText

Contains the response body as a string. Read-only.

responseXML

Contains the response body as XML. Read-only.

status

Contains the HTTP status code returned by a request. Read-only.

statusText

Contains the HTTP response status text. Read-only.

Table 3.6: XMLHttpRequest Object Methods for Apple Safari
Open table as spreadsheet

Object Method

Description

abort

Aborts the HTTP request.

getAllResponseHeaders

Returns all the HTTP headers.

getResponseHeader

Returns the value of an HTTP header.

open

Opens a request to the server.

send

Sends an HTTP request to the server.

setRequestHeader

Sets the name and value of an HTTP header.

Now that you have your required XMLHttpRequest object, how do you actually work with it? You start by opening it, which happens in the getData function.

Opening the XMLHttpRequest object

After creating your XMLHttpRequest object, it’s ready for use when the user clicks the button in the application’s page. The function tied to that button click is getData:

 <script language = "javascript">   function getData(dataSource, divID)   {     .     .     .   } </script>

It’s time to work with the XMLHttpRequest object you’ve created, connecting to the server and downloading your data. The getData function starts by checking if you’ve been able to create a valid XMLHttpRequest object, and if not, just quitting:

 function getData(dataSource, divID) {   if(XMLHttpRequestObject) {         .         .         .   } }

You can also display an error message to users if their browsers can’t handle Ajax (that is, can’t create an XMLHttpRequest object). You might do that something like this, where the code is using the ID of the target <div> element to get an object corresponding to that <div> element and setting the HTML in the element to the text “Sorry, your browser can’t do Ajax”:

 function getData(dataSource, divID) {   if(XMLHttpRequestObject) {         .         .         .   }   else {     var obj = document.getElementById(divID);     obj.innerHTML = "Sorry, your browser can't do Ajax.";   } }

The first step in working with an XMLHttpRequest object is to open it. Opening an XMLHttpRequest object configures it for use with the server; here’s how you use that method (items in square braces, [ and ], are optional):

 open("method", "URL"[, asyncFlag[, "userName"[, "password"]]])

Here are what these various parameters mean:

  • method: The HTTP method used to open the connection, such as GET, POST, PUT, HEAD, or PROPFIND.

  • URL: The requested URL.

  • asyncFlag: A Boolean value indicating whether the call is asynchronous. The default is true.

  • username: The user name.

  • password: The password.

Here’s how this example uses the XMLHttpRequest object’s open method. In this case, it uses the GET method to contact the server, and passes the URL of the file it’s looking for, which is passed to the getData function as the divID argument:

 function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     XMLHttpRequestObject.open("GET", dataSource);         .         .         . }

The two primary HTTP methods to access the server used with Ajax are GET and POST, and this example uses the GET method. You’ll see how to use POST later in this chapter; you work with the XMLHttpRequest object slightly differently when you use POST.

Note also that the URL passed to the open method is simply the name of the file, data.txt, which is passed to the getData function when the button is clicked:

 <form>   <input type = "button" value = "Fetch the message"     onclick = "getData('data.txt', 'targetDiv')"> </form>

To make this work, make sure that data.txt is in the same directory on the server as the application’s Web page, index.html. If it’s in another directory, such as the data subdirectory of the directory that index.html is in, you can use that as a URL:

 <form>   <input type = "button" value = "Fetch the message"     onclick = "getData('data/data.txt', 'targetDiv')"> </form>

Both “data.txt” and “data/data.txt” are relative URLs. You can also use an absolute URL, which specifies the full path to data.txt, something like this:

 <form>   <input type = "button" value = "Fetch the message"     onclick = "getData('http://localhost/chap03/data.txt',       'targetDiv')"> </form>

However, if you want to access data on a different domain than the one index.html comes from, like this:

 <form>   <input type = "button" value = "Fetch the message"     onclick = "getData('http://www.someOtherHost.com/data.txt',       'targetDiv')"> </form>

you’ll have problems because the browser considers this a security risk.

Cross-Ref 

More on this perceived security risk-and how to get around it-in Chapter 4.

To keep things simple, the examples in this book download data from the same directory that the Ajax-enabled page is in. For this example, that means you have to be sure to place data.txt in the same directory as index.html (they come in the same directory as the downloadable code for the book).

Handling data downloads

The data that comes from the server is handled asynchronously, that is, the application doesn’t hold everything and wait for it. After all, that’s what the big A in Ajax is: Asynchronous, right? What does that mean for you?

It means that you have to set up a callback function that will be called when data has been downloaded, or is in the process of being downloaded. The XMLHttpRequest object will call that callback function with news of the download.

You can assign a function to the XMLHttpRequest object’s onreadystatechange property to connect a callback function to the XMLHttpRequest object. That might look like this, where you’re assigning a function named callbackFunction to the onreadystatechange property:

 function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = callbackFunction;   } } function callbackFunction() {     .     .     . } 

However, that’s not the way it’s usually done in Ajax applications. In Ajax applications, you typically use an anonymous JavaScript function, which you create in-place with the function keyword, followed by the curly braces that you use to enclose the function’s body:

 function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = function()     {         .         .         .     }   } } 

These functions are called anonymous because they don’t have names: you just supply their code directly, inside the curly braces after the function keyword.

This new anonymous function handles data downloads for you; it’s notified when something happens, data-wise, with the server. The XMLHttpRequest object has two properties to check in the new anonymous function: the readyState property and the status property.

The readyState property tells you how the data downloading is coming. Here are the possible values for this property:

  • 0 uninitialized

  • 1 loading

  • 2 loaded

  • 3 interactive

  • 4 complete

A value of 4 is what you want to see because that means that the data has been fully downloaded.

The status property is the property that contains the actual status of the download. This is actually the normal HTTP status code that you get when you try to download Web pages. For example, if the data you’re looking for wasn’t found, you’ll get a value of 404 in the status property. Here are some of the possible values:

  • 200 OK

  • 201 Created

  • 204 No Content

  • 205 Reset Content

  • 206 Partial Content

  • 400 Bad Request

  • 401 Unauthorized

  • 403 Forbidden

  • 404 Not Found

  • 405 Method Not Allowed

  • 406 Not Acceptable

  • 407 Proxy Authentication Required

  • 408 Request Timeout

  • 411 Length Required

  • 413 Requested Entity Too Large

  • 414 Requested URL Too Long

  • 415 Unsupported Media Type

  • 500 Internal Server Error

  • 501 Not Implemented

  • 502 Bad Gateway

  • 503 Service Unavailable

  • 504 Gateway Timeout

  • 505 HTTP Version Not Supported

Note 

You’ll want to see a value of 200 here, which means that the download completed normally.

In the anonymous function, you can start by checking the XMLHttpRequest object’s readyState property to make sure that it’s 4, which means that the data download is complete:

 <script language = "javascript">         .         .         . function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = function()     {       if (XMLHttpRequestObject.readyState == 4         .         .         .       }     }   } } </script>

And you can also check that the XMLHttpRequest object’s status property holds a value of 200, meaning that the download went okay, like this:

 <script language = "javascript">         .         .         . function getData(dataSource, divID) {   if(XMLHttpRequestObject) {     XMLHttpRequestObject.open("GET", dataSource);     XMLHttpRequestObject.onreadystatechange = function()     {       if (XMLHttpRequestObject.readyState == 4 &&         XMLHttpRequestObject.status == 200) {         .         .         .       }     }      XMLHttpRequestObject.send(null);   } } </script>

So the download went okay, and it’s complete at this point; you got the data you asked for from the server, using Ajax. So how do you actually get access to that data?

Getting your data

If the XMLHttpRequest object’s status property holds 200 and its readyState property holds 4, you’ve downloaded your data. Cool. Now how do you get that data?

There are two ways, both involving the XMLHttpRequest object:

  • If the data you’ve downloaded is in simple text format, you read it from the XMLHttpRequest object’s responseText property, like this: XMLHttpRequestObject.responseText.

  • If you’ve downloaded data in XML format, you use the XMLHttpRequest object’s responseXml property, like this: XMLHttpRequestObject.responseXml.

In this case, you’ve downloaded text data from the data.txt file, so this application uses the responseText property. The goal of this first application is to display the fetched data in the Web page, and you can use a little dynamic HTML to do that. You start by getting a JavaScript object corresponding to the targetDiv <div> element in the Web page, which is where this example displays its text:

 <script language = "javascript">     .     .     .   function getData(dataSource, divID)   {     if(XMLHttpRequestObject) {       var obj = document.getElementById(divID);       XMLHttpRequestObject.open("GET", dataSource);       XMLHttpRequestObject.onreadystatechange = function()       {         if (XMLHttpRequestObject.readyState == 4 &&           XMLHttpRequestObject.status == 200) {             .             .             .         }       }     }   } </script>

Then you can display the fetch text, currently residing in the responseText property, in the targetDiv <div> element like this:

 <script language = "javascript">     .     .     .   function getData(dataSource, divID)   {     if(XMLHttpRequestObject) {       var obj = document.getElementById(divID);       XMLHttpRequestObject.open("GET", dataSource);       XMLHttpRequestObject.onreadystatechange = function()       {         if (XMLHttpRequestObject.readyState == 4 &&           XMLHttpRequestObject.status == 200) {             obj.innerHTML = XMLHttpRequestObject.responseText;         }       }     }   } </script>

So is that it? Are you ready to go? Not so fast, there’s one more step. You’ve configured the XMLHttpRequest object to connect to the server and download data.txt, and you’ve set up the code needed to handle the download when it happens. But you haven’t made that download happen yet, and that’s coming up next.

Downloading the data

You’ve configured the XMLHttpRequest object with its open method, and you’ve assigned an anonymous function to its onreadystatechange property. So how do you actually make the download happen by connecting to the server?

Use the XMLHttpRequest object’s send method to send your request to the server. When you use the GET HTTP method to connect to the server, everything is configured in the open method; you just send a value of null to the server using the send method. Here’s what that looks like in code:

 <script language = "javascript">   var XMLHttpRequestObject = false;   if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } else if (window.ActiveXObject) {     XMLHttpRequestObject = new       ActiveXObject("Microsoft.XMLHTTP");   }   function getData(dataSource, divID)   {     if(XMLHttpRequestObject) {       var obj = document.getElementById(divID);       XMLHttpRequestObject.open("GET", dataSource);       XMLHttpRequestObject.onreadystatechange = function()       {         if (XMLHttpRequestObject.readyState == 4 &&           XMLHttpRequestObject.status == 200) {             obj.innerHTML = XMLHttpRequestObject.responseText;         }       }       XMLHttpRequestObject.send(null);     }   } </script>

And that’s it! You’ve created your first Ajax application, and it functions as shown earlier in Figures 3.1 and 3.2.

Here’s a review of the steps you followed to create this first, important Ajax application:

  1. You created an XMLHttpRequest object.

  2. You used its open method to configure it.

  3. You connected an anonymous JavaScript function to handle the download to the XMLHttpRequest object’s onreadystatechange property.

  4. Because you were using the GET HTTP method to fetch data, you sent an argument of null to the server to actually start the data download.

This process is the essence of Ajax.

Now that you’ve completed your first Ajax application, it’s time to elaborate it. You’ve gotten a good foundation in the topic, but that’s only the beginning. For example, there are other ways of creating XMLHttpRequest objects.

More ways to create XMLHttpRequest objects

You’ve already seen one way of creating XMLHttpRequest objects in the Ajax application you’ve just completed, which looks like this in JavaScript:

 <script language = "javascript">   var XMLHttpRequestObject = false;   if (window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } else if (window.ActiveXObject) {     XMLHttpRequestObject = new       ActiveXObject("Microsoft.XMLHTTP");   }

In fact, you can elaborate this code because there is more than one way of creating XMLHttpRequest objects in Internet Explorer (and there’s more to say about creating XMLHttpRequest objects in Firefox and Mozilla when you’re working with XML, as discussed later in this chapter). This code is sufficient for the purposes in this book, but there are various versions of its XMLHttpRequest object available in Internet Explorer. You create the normal version of this object with the Microsoft.XMLHTTP ActiveX object, but there’s a more recent version available: MSXML2.XMLHTTP, and even newer versions, such as MSXML2.XMLHTTP.3.0, MSXML2.XMLHTTP.4.0, or now MSXML2.XMLHTTP.5.0.

The trick is to check whether or not your browser can create these recent XMLHttpRequest objects, without bringing the application to a screeching halt if it cannot. You can do that with the JavaScript try/catch construct, which lets you test sensitive code, and continue even if it fails. For example, say that you want to create an MSXML2.XMLHTTP object with a try statement like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;   try {     XMLHttpRequestObject = new       ActiveXObject("MSXML2.XMLHTTP");   } 

If the code that attempts to create the new XMLHttpRequest object fails, use a JavaScript catch statement. That’s how it works in general: if the code in a try statement fails, control passes to the following catch statement. Information, such as the reason the try statement failed, is passed to the catch statement using an exception object, like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;   try {     XMLHttpRequestObject = new       ActiveXObject("MSXML2.XMLHTTP");   } catch (exception1) {     .     .     .   } 

If the attempt to create an XMLHttpRequest object with ActiveXObject("MSXML2.XML-HTTP") failed, you can create an XMLHttpRequest object the standard way, using ActiveXObject("Microsoft.XMLHTTP") like this in a new try statement:

 <script language = "javascript">   var XMLHttpRequestObject = false;   try {     XMLHttpRequestObject = new       ActiveXObject("MSXML2.XMLHTTP");   } catch (exception1) {     try {       XMLHttpRequestObject = new        ActiveXObject("Microsoft.XMLHTTP");     }  }

If that works, fine, you have an XMLHttpRequest object. If not, however, you might want to explicitly set the XMLHttpRequestObject variable to false, which you can do like this:

 <script language = "javascript">   var XMLHttpRequestObject = false;   try {     XMLHttpRequestObject = new       ActiveXObject("MSXML2.XMLHTTP");   } catch (exception1) {     try {       XMLHttpRequestObject = new        ActiveXObject("Microsoft.XMLHTTP");     } catch (exception2) {       XMLHttpRequestObject = false;     }   } 

If the XMLHttpRequestObject variable is left with a value of false at this point, you know you’re not dealing with Internet Explorer, version 5.0 or later. That means you have to create an XMLHttpRequest object using the Mozilla/Firefox method this way in the sample application index2.html:

 <script language = "javascript">   var XMLHttpRequestObject = false;   try {     XMLHttpRequestObject = new       ActiveXObject("MSXML2.XMLHTTP");   } catch (exception1) {     try {       XMLHttpRequestObject = new        ActiveXObject("Microsoft.XMLHTTP");     } catch (exception2) {       XMLHttpRequestObject = false;     }   }   if (!XMLHttpRequestObject && window.XMLHttpRequest) {     XMLHttpRequestObject = new XMLHttpRequest();   } 

That’s it! This code creates an MSXML2.XMLHTTP XMLHttpRequest object in Internet Explorer, if possible.

Up to this point, you’ve downloaded some text from a text file, but Ajax is about more than that. Ajax is all about connecting to the server behind the scenes and interacting with that server. And that means working with programming on the server side.



Ajax Bible
Ajax Bible
ISBN: 0470102632
EAN: 2147483647
Year: 2004
Pages: 169

Similar book on Amazon
HTML, XHTML, and CSS Bible
HTML, XHTML, and CSS Bible
JavaScript Bible
JavaScript Bible
JavaScript Bible
JavaScript Bible
PHP and MySQL Web Development (4th Edition)
PHP and MySQL Web Development (4th Edition)

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net