Web JavaScript Archives

Asynchronous Form Submission

Problem

I developed this bit of JavaScript to allow me to create a user-friendly way to send form data to the server without taking the user from their current page. This way, a user can work on a large document and make periodic "saves" without breaking workflow.

Requirements

As with most general-purpose JavaScript, I had to find a balance between ease of programming and flexibility. I arrived at a very simple model to handle the asynchronous nature of "behind the scenes" HTTP requests:

interface_function(
    mixed context_parameter,
    function response_handler
);

Based on the application, the context parameter and the response handler may be used or controlled differently. For instance, the context parameter may be an ID for DOM reference or a DOM node itself. Another example is that a response handler may be passed either plain text, an array, or an XML DOM node from the response of the HTTP request.

In this application, the context is a form, so the ID of the form is used. The response from the server should be a simple message intended to tell the user the result of the request. Thus, the response handler should take a single parameter that's a string. At that point, the handler may use a dynamic node, alert box, or another form field to pass the message on to the user.

Submitting a Form in "Stealth Mode"

One of the great things about this code is that it doesn't need any special facility from the form or the form processor to operate as if it were a regular form submission. The client form can be the exact same form that is used for standard submission. The server-side processor can sometimes be the same script. The only problem is that a form processor usually responds with a web page or a redirect to a web page. This means the response string will be an entire page when all we want is a simple message. That's why the submission function will send a special query variable called asynch_submission set to true. This allows a server-side processor to tell if the form was submitted out of the usual flow and respond accordingly. Even then, the majority of a well-developed form processor should be nearly identical code making it possible to use a simple if statement at the end of the processor to either redirect or simply send the message.

Application Programming Interface

Something (a user, a timer, another script, etc) has to call the submission function with the ID of the form that is being submitted and a reference to an event handler that will "catch" the response from the server. The handler should take one argument that will contain the text. It's up to the handler to decide how to show it to the user or do something else exciting.

Example of the interface:

asynch_submit(
    'form_id',
    function(response) {
        alert(response);
    }
);

Example of Real Use

asynch_submit.example (674 bytes)  
<script type="text/javascript" src="/flat/javascript/http_client.js"></script>
<script type="text/javascript">
function get_response(response) {
    alert(response);
    document.getElementById('asubmit').disabled = false;
}
function make_request() {
    document.getElementById('asubmit').disabled = true;
    asynch_submit('form_id', get_response);
}
</script>
<form action="/flat/javascript/asynch_submit.php" method="post" id="form_id">
    <p>
        <input type="text" name="field_1" value="test field 1" />
    </p>
    <p>
        <textarea name="field_2">test
field 2</textarea>
    </p>
    <p>
        <input type="button" value="Quick Save Form"
            id="asubmit" onclick="make_request();" />
    </p>
</form>
asynch_submit.php (87 bytes)  
<?php

foreach($_REQUEST as $k => $v) {
    if(!is_array($v)) {
        echo "$k:$v\n";
    }
}

?>

Try the Example

The particulars of the submissions are determined from the form itself. The action and method are read from the form's attributes with reasonable defaults (as the browser would on its own).

It's important to note that the HTTP request won't always happen faster than a user is aware. The button that submits the form should probably be disabled during the request to keep a user from submitting it twice with a double click. When the response handler is called, the HTTP request has finished and the button can be enabled.

Code Listing

asynch_submit.js (2300 bytes)  
/**
 * asynch_submit
 * Submits any HTML form asynchronously.
 *
 * This script requires the use of my http_client class.
 *
 * @author Zac Hester
 * @date 2006-03-20
 * @version 1.0.0
 *
 * @param form_id The ID of the form to submit
 * @param handler A user-defined event handler taking one argument
 */      
function asynch_submit(form_id, handler) {

    //Reference the form and get settings.
    var frm = document.getElementById(form_id);
    var action = frm.getAttribute('action');
    var method = 'GET';
    var test = frm.getAttribute('method');
    if(test) { method = test.toUpperCase(); }

    //HTTP client.
    var as_http_client = new http_client();

    //Construct parameter list as a query string.
    var query = 'asynch_submission=true';
    query += as_get_qstring(frm.getElementsByTagName('INPUT'));
    query += as_get_qstring(frm.getElementsByTagName('SELECT'));
    query += as_get_qstring(frm.getElementsByTagName('TEXTAREA'));

    //Post requests.
    if(method == 'POST') {
        as_http_client.post(action, query, handler,
            function() {
                handler('Error: The connection to the server has timed out.');
            }
        );
    }

    //Get requests.
    else {
        as_http_client.get(action+'?'+query, handler,
            function() {
                handler('Error: The connection to the server has timed out.');
            }
        );
    }

    //We made it this far.
    return(true);
}


/**
 * as_get_qstring
 * Return a chunk of a query string for a DOM node list.
 *
 * @param nodes A DOM node list of form elements
 */  
function as_get_qstring(nodes) {
    var query = '';
    var ele = null;
    var type = '';
    for(var i = 0; i < nodes.length; ++i) {
        ele = nodes[i];
        if(ele.tagName == 'SELECT' && ele.name) {
            query += '&'+ele.name+'='
                +encodeURIComponent(ele.options[ele.selectedIndex].value);
        }
        else if(ele.tagName == 'TEXTAREA' && ele.name) {
            query += '&'+ele.name+'='+encodeURIComponent(ele.value);
        }
        else if(ele.tagName == 'INPUT' && ele.name) {
            type = ele.getAttribute('type');
            if(type == 'radio') {
                if(ele.checked) {
                    query += '&'+ele.name+'='
                        +encodeURIComponent(ele.value);
                }
            }
            else if(type == 'checkbox') {
                if(ele.checked) {
                    query += '&'+ele.name+'='
                        +encodeURIComponent(ele.value);
                }
            }
            else {
                query += '&'+ele.name+'='+encodeURIComponent(ele.value);
            }
        }
    }
    return(query);
}

Page last updated 2006-03-20