Ajax

Hans's picture
Thu, 2010-01-07 21:42 by Hans · Forum/category:

Part two: Ajax cross-domain

Introduction

Ajax is one of the most fascinating new technologies that revolutionized the presentation of dynamic web sites displaying server data.

Ajax is nothing more than a piece of JavaScript code that can exchange data with a web server in the background of a web page without requiring a complete reload of the page.

Ajax is quick and elegant. It can, for example, take user input and use it to fetch specific data from a web server. It can then format this data and incorporate it into the currently displayed web page.

All this can and usually does happen in the timeframe of a second. Ajax web pages therefore feel quicker than page reloads.

Technical details of Ajax

A key component is a web browser object that is easily accessible from JavaScript and allows synchronous or asynchronous HTTP connections between the web browser client and the web server. The name of this object is XMLHttpRequest, even though the exchanged data does not necessarily have to be in XML format. This object facilitates a complete HTTP request and response, allowing JavaScript code on the currently loaded web page to send a GET or POST request to the server and in turn receive a complete web page, some XML data, or any data in any other format, preferably JSON.

Older Internet Explorer versions before version 7 have a very similar ActiveX object, which is compatible and serves the same purpose.

For XML data the object contains a complete parser, allowing to navigate the XML tree. Any other format can be delivered as a string and parsed and processed in some other way.

The Ajax data exchange can be synchronous, i.e. the program waits until the response is received, then continues, or, preferably, asynchronous, i.e. the program fires of a HTTP request, then continues. The response later triggers a callback function that can process the data. The routine shown here supports only the more interesting and useful asynchronous mode.

Ostensibly for security reasons all modern browsers follow a same-origin rule. This means that Browsers can only load data by Ajax from the same domain the web page itself hails from. Since there are several other ways to achieve unlimited cross-domain scripting, this limitation makes very little sense, but we have to live with it for now.

Features of this Ajax implementation

The ajaxCall(…) routine shown below has the following features.

  1. Works with all browsers that can do Ajax at all, including older versions of Internet Explorer.
  2. Controls whether the browser caches the results.
  3. Can fetch data from the local file system in Internet Explorer 7 (useful for local testing).
  4. Continues to operate even after the user data processing code crashes.
  5. Can be called recursively by the user-supplied Ajax result processing routine.

Demo

The following three files comprise a complete Ajax demo with comments in the code. The Ajax functionality is encapsulated in one function.

On the click of a button this example loads the file ajax-testdata.txt in the background and displays its content in the existing, already loaded HTML page.

You can try the demo here:

[No data]

ajax-call.js

// Version 2010-06-15 Hans-Georg Michna

// Parameters
// getOrPost: Type of call - "GET" or "POST"
// url:       Server URL that is called, with query string
//            in case of "GET"
// nocache:   true to prevent that the browser caches the
//            called data.
// timeout:   Time in ms after which the Ajax call will
//            automatically be cancelled with error 902
// processAjaxResult: Name of a user-supplied function with
//            one parameter, the XMLHttpRequest. The function
//            must at least test whether XMLHttpRequest.status
//            is 0 or 200 before trying to process the result.
// postData:  Data string, URL-encoded, for "POST"
// username:  Username for a password-protected web page
// password:  Password for a password-protected web page

function ajaxCall(getOrPost, url, noCache, timeout,
    processAjaxResult, postData, username, password) {
  // var UNSENT = 0;
  // var OPENED = 1;
  // var HEADERS_RECEIVED = 2;
  // var LOADING = 3;
  var DONE = 4;
  if (getOrPost !== "GET" && getOrPost !== "POST") return;
  // Constructor for universal XMLHttpRequest:
  function XHR() {
    var activexmodes = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0",
        "Msxml2.XMLHTTP"]; // The last is for Internet Explorer 5.x.
    // If working with the local file system, try Microsoft's Msxml2
    // first, because Internet Explorer 7 also has an XMLHttpRequest,
    // which, however, fails with the local file system:
    if (location.protocol === "file:" &&
        typeof ActiveXObject === "function")
      for (var i = 0; i < activexmodes.length; i++)
        try {
          return new ActiveXObject(activexmodes[i]);
        } catch (ignore) {}
    try {
      return new XMLHttpRequest();
    } catch (ignore) {}
    for (var i = 0; i < activexmodes.length; i++)
      try {
        return new ActiveXObject(activexmodes[i]);
      } catch (ignore) {}
  }
  if (!ajaxCall.xhr) ajaxCall.xhr = new XHR();
  if (!ajaxCall.xhr) {
    // In case of an error a new object with the essential
    // properties status and statusText is constructed and
    // returned instead of the useless genuine one. The
    // processAjaxResult routine must always check the status
    // property at least for the success values 0 and 200:
    processAjaxResult({ status: 901,
      statusText: "Browser too old or not set up properly."
    });
    return;
  }
  ajaxCall.xhr.open(getOrPost, url, true, username, password);
  if (getOrPost === "GET" && noCache)
    try {
      ajaxCall.xhr.setRequestHeader("If-Modified-Since",
          "01 Jan 1970 00:00:00 GMT");
    } catch (ignore) {
      url += "&nocache=" +
          new Date().getTime().toString().slice(-11, -3);
    }
  if (getOrPost === "POST")
    try {
      ajaxCall.xhr.setRequestHeader("Content-Type",
          "application/x-www-form-urlencoded");
    } catch (ignore) {}
  var timeoutId;
  if (timeout) timeoutId = setTimeout(function () {
    // Cancel all other actions:
    ajaxCall.xhr.onreadystatechange = function () {};
    ajaxCall.xhr.abort();
    processAjaxResult({ status: 902,
      statusText: "Timeout after " + timeout * 0.001 + " s."
    });
  }, timeout);
  ajaxCall.xhr.onreadystatechange = function () {
    if (ajaxCall.xhr.readyState === DONE) {
      // Prevent a second call:
      ajaxCall.xhr.onreadystatechange = function () {};
      // Cancel the timeout:
      clearTimeout(timeoutId);
      // Call the user-supplied callback routine:
      processAjaxResult(ajaxCall.xhr.status === 1223 ?
        // Special case, Internet Explorer status 1223:
        { status: 204, statusText: "No Content" } :
        // Normal callback parameter:
        ajaxCall.xhr
      );
    }
  };
  try {
    ajaxCall.xhr.send();
  } catch (e) {
    ajaxCall.xhr.onreadystatechange = function () {};
    ajaxCall.xhr.abort();
    clearTimeout(timeoutId);
    processAjaxResult({ status: 903,
        statusText: "Could not send request. Exception " +
        e.name + ": " + e.message
    });
  }
}

Download ajax-call.js

ajax-sample.js

// Example code, to be replaced with your actual processing code:

function submitButtonClicked(url) {
  var button = document.getElementById("get-data-button");
  var displayElement = document.getElementById("display");
  if (button.value === "Get data") {
    // Get and display data:
    displayElement.innerHTML = "URL: " +
        url + "<br />";
    button.value = "Clear data";
    // Normally you want to set your timeout higher, depending on
    // the length of the data. Try 20000 (20 seconds):
    ajaxCall("GET", url, false, 2000, processAjaxResult);
  } else {
    displayElement.innerHTML = "[No data]";
    button.value = "Get data";
  }
}

function processAjaxResult(xhr) {
  // Demo display element:
  var displayElement = document.getElementById("display");
  // React to a failed Ajax call:
  if (xhr.status !== 0 && xhr.status !== 200) {
    // Demo display:
    displayElement.innerHTML += "<br />Error " +
        xhr.status + ": " + xhr.statusText;
    return;
  }
  // Ajax call succeeded. Process your data:
  displayElement.innerHTML += "<br />Result: <strong>" +
      xhr.responseText + "</strong>";
}

ajax-test.htm

<!doctype html>
<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<title>Ajax Demo</title>
<script src="ajax-source.js"></script>
<script src="ajax-sample.js"></script>
</head>

<body>

<h1>Ajax Demo</h1>

<input type="button" id="get-data-button" value="Get data"
    onclick="submitButtonClicked('ajax-testdata.txt');" />

<p id="display">[No data]</p>

</body>

</html>

ajax-testdata.txt

This is the content of the file: ajax-testdata.txt

Part two: Ajax cross-domain

You can also download the complete demo as one ZIP file.

AttachmentSize
ajax.zip (Contains all 4 files.)2.81 KB