LoJAX (Low-technology AJAX)

12th June 2006

LoJAX is a re-creation of the window.XMLHttpRequest object, designed for low-specification and legacy browsers.

Abstract illustration for this script: an antique wooden sign, with Japanese writing.

It can also be used in browsers that already have support, to provide additional functionality not available in any native implementation.

LoJAX uses a combination of client-side and server-side code to make real HTTP requests, which have the same context and permissions as regular XHR requests, and return proper headers and status information.

LoJAX supports GET, POST and HEAD, and provides all of the principle methods and events, like open(), send() and onreadystatechange. However it doesn't support synchronous requests, and more notably, there's no support for responseXML.

responseText is supported fine, allowing data to be received as plain text, HTML or JSON (or any text-based format), and although individual requests are always asynchronous, LoJAX has the ability to synchronize multiple requests on the same or multiple object instances, to ensure that each request is returned in the order it was made.

Since the actual HTTP request is made server-side, LoJAX also has the ability to retrieve data from external domains.

Browser support

The script is known to work in the following browsers:

  • Sony PSP (Version 2.00 or later)
  • Opera 5, 6 and 7 (Versions 5.0 to 7.5 inclusive)
  • Mac/IE5 (Version 5.0 or later)
  • Konqueror 3.1
  • OmniWeb 4.5 and 5.0
  • Espial Escape 5.1

This is in addition to being supported in all browsers which natively support XMLHttpRequest, and when used in these browsers it can be configured either to have a different function name, or to have same name and hence over-write the native object (except in Safari and IE7; please see the additional browser notes for details).

Get the script

Download the zipfile [18K] and unzip it; it contains all the script files you need, along with three test files for demonstration which you don't need to keep.

The core components of LoJAX are a JS script called "lojax.js", and a PHP script called "lojax.php". ("lojax.js" is compressed so that it loads faster, but there's also a fully-commented version included in the zipfile, for reference or further development.)

The only script you need to include directly on your page is the JavaScript (hence the page itself does not need to be parsed by PHP - LoJAX can be used on plain HTML pages, or those parsed by a different language).

The script can go anywhere on the page, and its functionality will be immediately available to any scripting that comes after it:

<!-- LoJAX (Low-technology AJAX) by brothercake - http://www.brothercake.com/ -->
<script type="text/javascript" src="lojax.js"></script>

For maximum usefulness overall, the optimum place to put the script is at the very top of the <head> section. However, since it uses procedural code to generate a <form> and <iframe> (which act as a data courier), it would actually invalidate the DOM on the fly to have it in the <head>.

I could argue on a purely pragmatic level, that isn't really so bad. The validator won't know the difference, and real people aren't affected - because crucially, the elements are generated in such a way that they can only be accessed programatically by the script - users can't reach them manually.

But a script-aware validating parser would be affected, so therefore, the script can only be safely used like this on pages served as text/html (because on pages in XHTML mode, a validating parser may throw a DOM exception and abandon the output).

Otherwise - the script will generate fully valid output if it's used in the <body> section of a page, having a doctype of XHTML 1.0 Transitional or earlier.

Client-side configuration

By default, the JavaScript will assume that the PHP script is in the same directory as the page it's being used on, and if this is true you're good to go.

But if the files are in different directories, or if you're using the script in different places within a site or application's heirarchy, you will need to define an absolute or web-root path to the PHP script. (This also means you can write your own server-side component in another language, and then specify the path to that.)

You can define the path using the optional client-side configuration file, "lojax-settings.js"; this must be added before "lojax.js" in the source code:

<!-- LoJAX (Low-technology AJAX) by brothercake - http://www.brothercake.com/ -->
<script type="text/javascript" src="lojax-settings.js"></script>
<script type="text/javascript" src="lojax.js"></script>

Then you can configure it. The path to the server-side script is the most significant setting, but there are four other options as well:

path to server-side script

The script works by submitting a form to a server-side script, and this value defines the path. The default value is a local path to the same directory, but if you're using the script in different places throughout a site or application, it's simplest to set a web-root path like "/lojax.php", or an absolute value like "http://www.domain.com/lojax.php".

You could also vary a local path on a per-instance basis, by having multiple copies of "lojax-settings.js" in different places.

what kind of implementation to use

The script's default behavior is to attempt to implement the re-creation only if a native XMLHttpRequest object is not found; this behavior is defined with the value "auto".

You can also set the value "lojax", which means to use the re-creation for all browsers irrespective of native support; or the value "none" to disable this script entirely.

client time-out for abandoning a request (seconds)

Specify a value in seconds, for how long the script should wait before abandoning a request.

expose the courier mechanism

Specify true or false for whether to expose the generated <form> and <iframe>. This is a development setting and should always have the value false in production use.

try to negate iframe history events

Every time the script makes a data request through the courier <iframe>, a history event may be generated. Whether or not it actually is generated varies widely by browser and circumstances, and so the script attempts to find a safe path through these quirks and negate as many history events as it can.

The purpose of this is so that when a user presses the Back button, the top-level page will go back, rather than it stepping through pages in the hidden <iframe>.

But the negation errs on the side of caution - if an event is not known to be safe, it will not be negated, and in this case "not safe" means there's a possibility the top-level page will go back, instead of the <iframe> page; this is clearly not acceptable, hence the need to be cautious.

So even if with this setting enabled (set to true) it's possible that residual events are still generated, resulting in the occasional need for users to click the back button twice, to go back to the previous page.

lojax function name

You can specify the function name which is used for the re-creation object. The default value is obviously "XMLHttpRequest", but you can call it anything you like.

This might be useful if you wish to separate scripted functionality between devices using this implementation and those using the native object, or if you wish to implement the framework for all browsers without over-writing their native object.

It's also necessary if you want to implement the framework in later builds of Safari (1.2 or later), or in Internet Explorer 7, in which it's not possible to over-write the native object, and therefore it will only work if the object has a different name (see additional browser notes).

Server-side configuration

By default, the PHP script is configured to reject requests outside its host domain. This is necessary for security, because an open framework would amount to a free proxy for spammers.

But you can specify additional hosts using the optional server-side configuration file, "lojax-hosts.php". If this file exists in the same directory as "lojax.php", and contains a valid $lojax_hosts array of one or more host names, then the script will be allowed to make requests to any of the specified hosts. For example:

<?php

//list host names or IP addresses
//to which the script is allowed to make requests
$lojax_hosts = array(
    'www.mysite.com',
    'www.myothersite.com'
    );

?>

What's the point?

That's a good question!

I originally wrote this because I wanted the animated songpointer in my iTunes web interface to work on the Sony PSP; yet in that specific case I still haven't succeeded, due to the severe limitations of DOM scripting generally in that browser.

And that's the rub - what's the point of extending AJAX functionality to devices which don't have the complementary DOM support?

Clearly in many cases, the answer is none at all, and I'm certainly not suggesting that everyone should start trying to making all their applications work in these ancient and low-tech devices!

But it's nice to have the option, and it's for niche applications that this script really comes into its own. Maybe you need to support the PSP, Mac/IE5 or Opera 7, for whatever reason. Maybe you need data from several domains, or you'd like a managed solution that provides control over the order in which requests are returned. The LoJAX framework:

  • opens up AJAX-based scripting to a wider range of devices
  • can be configured to retrieve data from anywhere on the internet
  • provides a platform for synchronising multiple requests

There's nothing revolutionary in all of this - there are plenty of alternative RPC techniques, that can be used in browsers which don't support XMLHttpRequest, or to bypass its normal limitations.

But LoJAX abstracts the mechanics into a familiar JavaScript object, which is instantly easy to use because there's no new syntax to learn.

Specification

The implementation is based on the retro-specification drafted by the W3C, which itself appears to be largely based on what Firefox does. However there are missing features, as well as additional features, and there are deviations from the letter of the specification in places.

Core features supported

  • GET, POST and HEAD requests
  • open(), send(), abort(), getAllResponseHeaders() and getResponseHeader() methods
  • readyState, responseText, status and statusText properties
  • four onreadystatechange events per request (one each of states 1 to 4)
  • 301, 302, 303 or 307 redirects are followed (up to five per request)
  • username and password authorization (but see the security note below)
  • same-origin security restrictions apply by default

Unique features in LoJAX

  • same-origin security restrictions can be overridden on a per-host basis
  • browser progress bar is always shown when a request is made
  • multiple requests are buffered to return in the same order they were made
  • additional open()..send() commands can be made without re-instantiating the request object
  • two custom error codes may be returned: 466 (Host Not Allowed) when attempting to make a request outside the current host or list of allowed hosts; and 467 (Unsupported Protocol) when attempting to make a request using an unsupported protocol (such as https)

Features not supported but safe to use

  • setRequestHeader() method exists so that it can be used without errors where native implementations require it for POST requests, but it doesn't actually do anything

Missing features

  • responseXML is not supported (it's always null)
  • abort() cannot physically stop a request, it merely resets and stops watching the current request object - the server-side process is uncontrollably asynchronous at this point, and data may be subsequently returned, but it will be ignored
  • binary submission using multipart/form-data is not supported
  • headers are only available if the readyState is 3 or greater
  • no support for sending an XML document with the send() method
  • status and statusText, when readyState is an inappropriate value, will return null instead of raising a DOM exception
  • the challenge/response behavior for password protected resources is not exactly the same - if you don't send the right details in the original request, the browser won't prompt you to enter them, you'll just get a 401

Other varations from native XHR

  • requests made from the server-side script are HTTP 1.0
  • the returned headers may not be identical to those of a native XHR request; for example, they won't include "keep-alive" or "TE" because the connection type will be "close" not "keep-alive"; and additional values may be present, such as "set-cookie"

Security note

If a request includes username and password authorization, this information will be sent unencoded to the "lojax.php" script. Although JavaScript can do Base 64 encoding, the process requires bit-shifting, which many of the older supported browsers can't handle (such as Mac/IE5).

Additional browser notes

LoJAX is not supported in Safari 1.0 or 1.1. Although the core scripting works, the interface breaks the back button (pressing Back reloads the current <iframe> page, instead of going back). I don't know of any way to fix this, so the script has to be specifically disabled.

In Safari 1.2 or later and Internet Explorer 7 (both of which already support XMLHttpRequest), the script cannot over-write the native object; so if you wish to use the LoJAX framework for these browsers, you will need to define a function name other than "XMLHttpRequest".

Discuss this page

Comments? Suggestions? Join the discussion!

Get the script

BSD License → Terms of use

Categories...

Website gadgets

Bits of site functionality:

Usability widgets

Local network apps

Web-applications for your home or office network:

Game and novelties

Our internal search engine is currently offline, undergoing some configuration changes in preparation for a major site overhaul. In the meantime, you can still search this site using Google Custom Search.


In this area

Main areas


[brothercake] a round peg in a square hole, that still fits