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
<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>
<?php
foreach($_REQUEST as $k => $v) {
if(!is_array($v)) {
echo "$k:$v\n";
}
}
?>Try the Example
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
* 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);
}