The first alternate Ajax technique is dynamic script loading. The concept is simple: create a new
<script/> element and assign a JavaScript file to its src attribute to load JavaScript that isn't initially written into the page. The beginnings of this technique could be seen way back when Internet Explorer 4.0 and Netscape Navigator 4.0 ruled the web browser market. At that time, developers learned that they could use the document.write() method to write out a <script/> tag. The caveat was that this had to be done before the page was completely loaded. With the advent of the DOM, the concept could be taken to a completely new level.The Technique
The basic technique behind dynamic script loading is easy, all you need to do is create a
<script/> element using the DOM createElement() method and add it to the page:var oScript = document.createElement("script");oScript.src = "/path/to/my.js";document.body.appendChild(oScript);Downloading doesn't begin until the new
<script/> element is actually added to the page, so it's important not to forget this step. (This is the opposite of dynamically creating an <img/> element, which automatically begins downloading once the src attribute is assigned.)Once the download is complete, the browser interprets the JavaScript code contained within. Now the problem becomes a timing issue: how do you know when the code has finished being loaded and interpreted? Unlike the
<img/> element, the <script/> element doesn't have an onload event handler, so you can't rely on the browser to tell you when the script is complete. Instead, you'll need to have a callback function that is the executed at the very end of the source file.Example 1
Here's a simple example to illustrate dynamic script loading. The page in this example contains a single button which, when clicked, loads a string ("Hello world!") from an external JavaScript file. This string is passed to a callback function (named
callback()), which displays it in an alert. The HTML for this page is as follows:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Example 1</title> <script type="text/javascript">//<![CDATA[ function makeRequest() { var oScript = document.createElement("script"); oScript.src = "example1.js"; document.body.appendChild(oScript); } function callback(sText) { alert("Loaded from file: " + sText); } //]]> </script> </head> <body> <input type="button" value="Click Me" onclick="makeRequest()" /> </body></html>The JavaScript file example1.js contains a single line:
callback("Hello world!");When the button is clicked, the
makeRequest() function is called, initiating the dynamic script loading. Since the newly loaded script is in context of the page, it can access and call the callback() function, which can do use the returned value as it pleases. This example works in any DOM-compliant browsers (Internet Explorer 5.0+, Safari, Firefox, and Opera 7.0+), try it for yourself or download the examples.8.1 More Complex Communication
Sometimes you'll want to load a static JavaScript file from the server, as in the previous example, but sometimes you'll want to return data based on some sort of information. This introduces a level of complexity to dynamic script loading beyond the previous example.
First, you need a way to pass data to the server. This can be accomplished by attaching query string arguments to the JavaScript file URL. Of course, JavaScript files can't access query string information about themselves, so you'll need to use some sort of server-side logic to handle the request and output the correct JavaScript. Here's a function to help with the process:
function makeRequest(sUrl, oParams) { for (sName in oParams) { if (sUrl.indexOf("?") > -1) { sUrl += "&"; } else { sUrl += "?"; } sUrl += encodeURIComponent(sName) + "=" + encodeURIComponent(oParams[sName]); } var oScript = document.createElement("script"); oScript.src = sUrl; document.body.appendChild(oScript);}This function expects to be passed a URL for a JavaScript file and an object containing query string arguments. The query string is constructed inside of the function by iterating over the properties of this object. Then, the familiar dynamic script loading technique is used. This function can be called as follows:
var oParams = { "param1": "value1", "param2": "value2"};makeRequest("/path/to/myjs.php", oParams)Handling Errors
The
XMLHttpRequest object has a huge upside: it allows JavaScript to communicate directly with the server without loading a page in the browser. Unfortunately, however, that also becomes its downside when the inevitable Bad Things happen on the back end. If you usually work with languages that can return errors directly in the browser window, it can feel like flying blind when you're trying to debug a page that uses the XMLHttpRequest object--especially in environments where you don't have easy access to server error logs.The object does have a
status property, which contains the numeric code returned by the server (for example, 404, 500, and 200), and there is an accompanying statusText property, which is a brief string message. In the case of a server-side (code 500) error, this message merely states the obvious "Internal Server Error," which might be OK to display to the user but is pretty worthless for debugging.The normal 500 error page returned from the server quite often contains extremely helpful debug information such as the error type, the line number on which the error occurred, and even a full backtrace of the error. Unfortunately, with the
XMLHttpRequest object, all that stuff ends up buried in a JavaScript string variable.It's actually fairly simple to retrieve the full-page 500 error messages and to accommodate debugging in a more elegant way. To accomplish this I had to add code to the original function for creating and using the
XMLHttpRequest object:if (xmlHttpReq.readyState == 4) {
strResponse = xmlHttpReq.responseText;
switch (xmlHttpReq.status) {
// Page-not-found error
case 404:
alert('Error: Not Found. The requested URL ' +
strURL + ' could not be found.');
break;
// Display results in a full window for server-side errors
case 500:
handleErrFullPage(strResponse);
break;
default:
// Call JS alert for custom error or debug messages
if (strResponse.indexOf('Error:') > -1 ||
strResponse.indexOf('Debug:') > -1) {
alert(strResponse);
}
// Call the desired result function
else {
eval(strResultFunc + '(strResponse);');
}
break;
}
}
The
case statement handles the response from the server differently depending on thexmlHttpReq.status value. It hands off the response to an error-handling function in the case of a full-blown error, but it still allows for a simple JavaScript alert to display a simple error message to the user (perhaps, "Error: e-mail address does not match the one for this account."), or to print a little debug message for the developer. (You could also print out those types of errors in nicely formatted text to a div somewhere on the page.)Here's the function that creates the full-screen 500 error page:
function handleErrFullPage(strIn) {
var errorWin;
// Create new window and display error
try {
errorWin = window.open('', 'errorWin');
errorWin.document.body.innerHTML = strIn;
}
// If pop-up gets blocked, inform user
catch(e) {
alert('An error occurred, but the error message cannot be' +
' displayed because of your browser\'s pop-up blocker.\n' +
'Please allow pop-ups from this Web site.');
}
}
The
try/catch is important because of the ubiquity of pop-up blockers. If the user has blocked pop-ups, at least she has the option of allowing them so that she can see and report the error properly.If the user allows pop-ups, a server-side 500 error will produce a new window containing the all-too-familiar page displaying all that helpful info you need to track down and squash the bug. (You might decide you'd rather display the error message in the current window, but my playlist search is a small pop-up window that will not display the entire message properly. Most standard-issue 500 error pages assume a full-size window.
No comments:
Post a Comment