2010-11-13

Avoiding URL length limits when loading pages with Javascript

Working on a web application that must work with IE, I encountered problems with opening windows (via window.open), or loading content in iframes, that required a large set of parameters to the URL provided. IE has a hard-coded limit on the size of URL strings, so parameter data could get clipped.

Also, it is not pretty to have a large URL string showing in the address bar.

Therefore, the solution was to use an HTTP POST based method.

My first attempt was to use AJAX with a POST request to retrieve the desired page, and then do a document.write() into a blank window. However, I discovered that IE does not process data in a serial manner via document.write(). For example, if the page content contains <script> elements referencing external resources to load, IE will not process those in sequential order, so if you have Javascript code that calls a function that is defined by a previous <script>, you can get a runtime error. IE will load the external javascript resources, but they are done asynchronously, with IE not waiting to execute subsequent Javascript in the page that comes after the <script>.

The solution I came up with is to create a transient page that performs a form post. I.e. In the blank window, I create dynamic HTML page that contains an HTML form with the set of parameter data defined as hidden fields. The HTML page created has an onLoad action to submit the form.

With this approach, the browser is directly fetching the resource, so the document.write() problem described earlier (for IE) is no longer a factor.

What follows is a function for loading a document using a POST-based method:

loadDocument = function(
/*Object*/args
)
{
// summary:
// Load a document for a given document node.
// description:
// This function uses a POST-style method for loading the
// contents of a document node, where the document requested
// takes an arbitrary number of request parameters. This
// function works-around limitations of some browsers where
// there is a limit to the length of query-string parameters
// for GET-based requests.
//
// args: Object
// Object contain the following properties:
//
// doc: Node
// Document node to load content for.
// url: String
// URL to fetch content from.
// params: Object?
// Properties of string values representing parameters
// for the request denoted by url.
// message: String?
// Message text (HTML) to display while content is
// loading.

var doc = args.doc;
doc.open();
doc.write('<html>');
doc.write('<script type="text/javascript">');
doc.write('function submitForm(){document.forms[0].submit();}');
doc.write('</script>');
doc.write('<body style="background-color:#FFF;" onLoad="submitForm()">');
if (args.message) {
doc.write(args.message);
}
doc.write('<form method="post" action="');
doc.write(args.url);
doc.write('">');
if (args.params) {
for (var p in args.params) {
var v = args.params[p];
if (v instanceof Array || typeof v == "array") {
for (var i=0; i < v.length; ++i) {
doc.write('<input type="hidden" name="');
doc.write(p);
doc.write('" value=\'');
doc.write(escapeHTML(v[i]));
doc.write('\'></input>');
}
} else {
doc.write('<input type="hidden" name="');
doc.write(p);
doc.write('" value=\'');
doc.write(escapeHTML(args.params[p]));
doc.write('\'></input>');
}
}
}
doc.write('</form></body></html>');
doc.close();
}

// Helper function to escape HTML specials for use above.
escapeHTML = function(/*String*/s) {
// summary: Convert HTML special characters to entity references.
// s: String to escape.
if (!(typeof s == "string" || s instanceof String)) {
s = s + ""; // force it to a string
}
return s.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}


The primary function works on a Document object, allowing it to not just be used for new windows, but for any object that contains a Document object, like frames and iframes.

Simple example using the above:

var w = window.open("");
loadDocument({
doc: w.document,
url: "http://example.com/some/resource/",
params: {
p1: "Hello",
p2: "World!"
}
message: 'Loading..."
});