Post Archive

› January 3, 2006

The Perfectionist's Guide to Building a Query String

  • Reported by Dave

Below is a little JavaScript utility I wrote while home with the flu over the holidays (yay immune system!). I have found it to be useful and thought I would pass it along. All it does is return a string in a format suitable for the query portion of a URL for a given form element. You will find this kind of functionality in just about any AJAX code library, but this one is infused with my own brand of anal-retentive attention to detail. I wanted to recreate, as close as possible, the way in which a browser constructs a query string when a form is submitted. I found that the rules for building the name/value pairs for the various kinds of form inputs were not immediately obvious.

Here is the script:

function toQueryComponent(input) {
    if (!input.name || input.disabled)
        return "";

    var n = urlencode(input.name);

    switch (input.type) {
    case "text":
    case "password":
    case "submit":
    case "hidden":
        return n + "=" + urlencode(input.value);
    case "textarea":
        // normalize line breaks as CR LF pairs as per RFC 1866
        var v = input.value.split(/\r\n|\r|\n/).join("\r\n");
        return n + "=" + urlencode(v);
    case "checkbox":
    case "radio":
        if (!input.checked)
            return "";
        var v = getRealValue(input);
        if (v === null) v = "on";
        return n + "=" + urlencode(v);
    case "select-one":
    case "select-multiple":
        var nvp = [];
        var opt, i = 0;
        while ((opt = input.options[i++]) != null) {
            if (opt.selected) {
                var v = getRealValue(opt);
                if (v === null) v = opt.text;
                // older versions of IE do not support Array.push
                nvp[nvp.length] = n + "=" + urlencode(v);
            }
        }
        return nvp.join("&");
    default:
        // input types reset, button, image, and file not implemented
        return "";
    }
}

function urlencode(str) {
    var v;
    try { v = encodeURIComponent(str); } catch (e) { v = escape(str); }
    return v.replace(/%20/g,"+");
}

function getRealValue(input) {
    var attr = input.getAttributeNode("value");
    return (attr && attr.specified) ? input.getAttribute("value") : null;
}

The script is fairly self-explanitory, but a few items are worth noting.

  • All input types become translated as name=value except for multi-select fields, which can have multiple name/value pairs delimited by ampersands.
  • While browsers may differ on the character(s) they use to represent line breaks in textarea fields, the HTML standard calls for them to be sent as carriage return/line feed pairs (ASCII 13 10).
  • If certain input types do not have an explicit value attribute, some default value is sent. For radio buttons and check boxes, this value is "on". For select fields, it is the display text.
  • Since the empty string ("") is a valid value for an input, it is important to pass this value to the query string instead of using any default, if it is specified. However, testing input.value cannot differentiate between an input with no value attribute and one with value="". The getRealValue utility function returns null if the value attribute dot not exist, which informs the script to use the default.
  • The way the browser encodes unicode characters differs from the result I get from the urlencode function. I am not sure why.

Comments

1. January 3, 2006 03:37 PM

Quote this comment

Dave Posted…

I suppose I should give an example of how to use this to build an entire query string:

function buildQueryString(form) {
    var str = "";
    var element, i = 0;
    while ((element = form.elements[i++]) != null) {
        var qc = toQueryComponent(element);
        if (qc != "") str += "&" + qc;
    }
    return str.substring(1);
}

var form = document.getElementById("someform");
var query = buildQueryString(form);
// append query to some URL and GET via XMLHttpRequest, etc.

2. January 4, 2006 11:03 AM

Quote this comment

Jan Korbel Posted…

Now... that looks useful! Thank you

3. January 10, 2006 03:05 PM

Quote this comment

Eru G Posted…

Awesome - really looks handy, thanks

4. May 12, 2006 05:24 PM

Quote this comment

Kamens Posted…

Thanks for this useful tool! I believe there may be one change to be made. IE's treatment of String.split is different than Firefox, etc. For instance, if you run

"511115".split(/1/);

on Firefox, you'll get an array w/ 5 separate elements, whereas in IE you'll get an array w/ 2 elements (b/c IE does not generate empty-string elements between all the matching 1's).

This means that the line:

var v = input.value.split(/\r\n|\r|\n/).join("\r\n");

will result in lost newlines in IE if you have a textarea and insert multiple consecutive newlines (since the join statement won't have as many elements to join on as FF does).

A suggested fix is:

var v = input.value.replace(/(\r\n)|\r|\n/g, "\r\n");

Which doesn't suffer from the IE/FF .split difference.