- 2.1 XMLHttpRequest Overview
- 2.2 Cross-Browser XMLHttpRequest
- 2.3 Sending Asynchronous Requests
- 2.4 AJAX Without XMLHttpRequest
- 2.5 Fallback Option 1: Sending a Request Using an IFrame
- 2.6 Fallback Option 2: Sending a Request Using a Cookie
- 2.7 Summary
2.5 Fallback Option 1: Sending a Request Using an IFrame
IFrames make a suitable transport for asynchronous calls because they can load content without causing the entire page to reload, and new IFrame elements can be created using JavaScript. The nicest attribute about an IFrame is that a form can use one as its target, reloading that IFrame instead of the entire page; this approach allows large amounts of data to be sent to the server using POST.
One difficulty in using an IFrame as a transport is that the page we're loading needs to be HTML, and it needs to have a JavaScript onload event handler to tell the parent document when it's done loading. This need forces all requests being made with IFrames to be made to pages designed to deal with IFrame requests. (Code can't just grab an XML file in the way that XMLHttpRequest allows.)
Note that the use of IFrames does have a number of further limitations:
- Support of only asynchronous requests
- Server pages needing changed
- Phantom entries in browser's history
- Odd back/forward button behavior in some browsers
- Large differences in browser implementations, especially in older browsers
One advantage that an IFrame has over XMLHttpRequest is that it can be used to make file uploads. Due to browser security limitations, only user actions, such as clicking a form, can interact with files on the user's machine. This makes targeting a form to an IFrame the only option for file uploads that do not involve a normal form POST and page reload cycle. However, there is no reason you can't fall back to using an IFrame for file uploads and XMLHttpRequest for the rest of your AJAX requests. Unless you are making remote scripting-style AJAX requests (which is covered in Chapter 3, "Consuming the Sent Data"), working around IFrame limitations will add a significant amount of work to any AJAX development project.
2.5.1 Creating a Hidden IFrame
To get maximum compatibility with older browsers, you could just add the IFrame to your HTML and give it a size of 0x0. (You can't just hide it, or some browsers won't load it.) However, this approach isn't flexible, so you will want to create the frame dynamically. Not all older browsers support document.createElement, but browsers without that support will generally lack the other dynamic capabilities needed to use the data you're loading, so it's best to provide support to them with a static HTML version of the page. In the following example, the IFrame is created using innerHTML because it's simpler than creating it using DOM methods. Note, however, that it could also be created with document.createElement, just like the div to which it's being added:
1 var rDiv = document.createElement('div'); 2 rDiv.id = 'remotingDiv'; 3 var style = 'border:0;width:0;height:0;'; 4 rDiv.innerHTML = "<iframe name='"+id+"' id='"+id+"' 5 style='"+style+"'></iframe>"; 6 7 document.body.appendChild(rDiv);
2.5.2 Creating a Form
If you want to make only a GET request, you can change the value of the IFrame's src property, but to do POST, you need to use a targeted form. GET isn't a good solution for AJAX requests for two reasons: it can send only a limited amount of data (an amount that changes depending on the browser), and GET can be cached and/or preloaded by proxy servers, so you never want to use it to perform an action such as updating your database.
Using a form with an IFrame is easy. Just set the form's target attribute, and when you submit the form, the result loads in the IFrame. The following example creates our form and sets its targets to the IFrame we created earlier in the "Creating a Hidden IFrame" section of the chapter:
1 rDiv.form = document.createElement('form'); 2 rDiv.form.setAttribute('id', id+'RemotingForm'); 3 rDiv.form.setAttribute('action', url); 4 rDiv.form.setAttribute('target', id); 5 rDiv.form.target = id; 6 rDiv.form.setAttribute('method', 'post'); 7 rDiv.form.innerHTML = '<input type="hidden" name="data" 8 id="'+id+'Data">';
2.5.3 Send Data from the Loaded Content to the Original Document
The only way to know that the content of the IFrame has loaded is to have the content page run some JavaScript that notifies the parent page in which the IFrame is embedded. The simplest way to do this is to set the onload event handler on the document you are loading. This limitation means you can't use an IFrame for loading arbitrary content like you can with XMLHttpRequest. However, it's still useful for cases in which a single server page is already being used as an AJAX gateway. Here is an example of onload:
<body onload="parent.document.callback(result)">
2.5.4 Complete IFrame AJAX Example
A full example of an IFrame that AJAX requests includes two pieces. The first piece is the client-side code to create the IFrame and form. The second piece is the server-side code, which prepares some data and sends it back to the parent document in its onload event handler.
The first part of the example (Listing 2-5) is the JavaScript code in a simple HTML file. This page is used for testing; the callback function just alerts the contents of the results. The second part of the example (Listing 2-6) is a simple PHP script, which takes the data from POST and sends it back to the parent document. To make a useful system, you might also want to include some extra variables in the form, which would tell the PHP code what to do with the uploaded data, or you could put the logic directly into the script and use a different target page for each task you wanted to accomplish.
Listing 2-5. Making an AJAX Request Using an IFrame
1 <html> 2 <head> 3 <script type="text/javascript"> 4 var remotingDiv; 5 function createRemotingDiv(id,url) { 6 var rDiv = document.createElement('div'); 7 rDiv.id = 'remotingDiv'; 8 var style = 'border:0;width:0;height:0;'; 9 rDiv.innerHTML = "<iframe name='"+id+"' id='"+id+"' 10 style='"+style+"'></iframe>"; 11 12 document.body.appendChild(rDiv); 13 rDiv.iframe = document.getElementById(id); 14 15 rDiv.form = document.createElement('form'); 16 rDiv.form.setAttribute('id', id+'RemotingForm'); 17 rDiv.form.setAttribute('action', url); 18 rDiv.form.setAttribute('target', id); 19 rDiv.form.target = id; 20 rDiv.form.setAttribute('method', 'post'); 21 rDiv.form.innerHTML = '<input type="hidden" name="data" 22 id="'+id+'Data">'; 23 24 rDiv.appendChild(rDiv.form); 25 rDiv.data = document.getElementById(id+'Data'); 26 27 return rDiv; 28 } 29 30 function sendRequest(url,payload,callback) { 31 if (!remotingDiv) { 32 remotingDiv = createRemotingDiv('remotingFrame', 33 'blank.html'); 34 } 35 remotingDiv.form.action = url; 36 remotingDiv.data.value = payload; 37 remotingDiv.callback = callback; 38 remotingDiv.form.submit(); 39 40 } 41 42 function test() { 43 sendRequest('test.php','This is some test data', 44 function(result){ alert(result) }); 45 } 46 47 48 49 </script> 50 </head> 51 52 <body id="body"> 53 54 55 <a href="javascript:test()">Test</a> 56 57 </body> 58 </html>
Listing 2-5 is made up of three functions:
- createRemotingDiv for setting up the IFrame.
- sendRequest for making an AJAX request.
- test for making an AJAX request. The test function is tied to a link (line 55) in the pages' HTML. Clicking on this link allows the user to start an AJAX request.
The createRemotingDiv function (lines 5–28) combines the previously described code for creating a hidden IFrame with the code for creating a form to submit to it. When the form is created, it's targeted against the newly created IFrame, making the form submission use it instead of reloading the current page. Showing the IFrame during development is often useful in the debugging process so that you can see any output generated by the page you're calling. You can do this by editing the style on line 8 and changing it to width:200;height:200;.
The sendRequest function (lines 30–40) makes an AJAX request. It takes the URL to which to make the request, a payload to send to the server, and a callback function to run when the request is complete. The function uses createRemotingDiv to set up the process (lines 31–34). Then sendRequest updates the action on the IFrame form (line 35), adds the payload value on the form, and submits the form using the IFrame. When the new page is loaded into the IFrame, the new document uses a JavaScript onload handler to call the callback function that was passed into the sendRequest method. The PHP page that processes the form POST and creates the onload JavaScript is shown in Listing 2-6.
Listing 2-6. PHP Server Page That Handles an IFrame AJAX Request
1 <html> 2 <head> 3 <script type="text/javascript"> 4 var result = "<?php 5 echo $_POST['data']; 6 ?>"; 7 </script> 8 </head> 9 <body 10 onload = 11 "parent.document.getElementById('remotingDiv').callback(result)"> 12 </body> 13 </html>
On the server side, the form is processed and output is created in the form of an HTML page. The simplest way to add new data is to generate JavaScript containing the new data. In this case, we are just echoing the data back to the client by putting it in the result variable (lines 4–6). Normally, you'll be running server-side code here and either outputting a string (as in this case) or adding new JavaScript code to run against the parent document. The callback function on the parent is called by the onload handler on the body tag (line 11).