Here are a couple of examples showing RPC using the XHR object (AJAX) to retrieve information from a database. When you click the button labeled "Find", it sends either zip code or city and state entered to the server-side script as search part of the URL passed to the XHR object.
Server-side code looks up the corresponding city and state or zip code in a database, returns the data, and the fields are filled in.
Try it out. Enter a zip code (or city and state in second example) and click the appropriate button. The database is old and missing some codes so you may have to try a couple before getting a city and state.
This would be a lot more difficult to do on the client. The database has 42K records. It would be possible to have the data in an array, but not practical. The initial page load would take a while even with a high speed connection and the search slow.
Only the function handleResponse presented in the JavaScript explanation is changed.
There is a twist in handling the returned data. The data is received in JSON format. The text string returned looks like {city: 'city-name', state: 'IL'}. This is an object literal. Let's see how that is used in processing
function handleFormResponse(){ if (HTTPObj.readyState == 4 || HTTPObj.readyState == "complete") { try { if (httpReq.status == 200) { var id = (HTTPObj.targetId) ? HTTPObj.targetId : HTTPTarget; expForm = document.getElementById(id); eval("var jsObj = " + HTTPObj.responseText); for (name in jsObj) { expForm[name].value = jsObj[name]; } } else (HTTPObj.readyState != "complete" { /* This branch is only valid when used with the XMLHttprequest * object. Then readyState will be a number. With iframes, * IE triggers the onreadystatechange event before * we're ready and would display this alert and then * finish the process. */ alert(HTTPObj.status + ": " + HTTPObj.status.Text); } } catch (e) { alert("Network error!\nPlease try later."); } } } //eof handleFormResponse
The highlighted lines of code are generic and can populate small or large forms thanks to JSON.
The line, eval("var jsObj = " + HTTPObj.responseText), converts the return data in httpObj.responseText into a JavaScript object named jsObj. Its properties, like properties of all objects, can be accessed with square bracket notation. All the named inputs of a form (the form's elements collection) can be accessed the same way. The syntax is formObj.elements['field_name'] or formObj['field_name].
By naming the properties in the return data with the same names as the form input field, it's possible to use a for-name-in loop to step through all properties in the data assigning their values to the fields. This short code will update any number of text or textarea fields. Select list, checkbox, and radio fields require a little extra handling.
An IE 5.0 and 6.0 bug, with the combination of ActiveX, for-name-in and framesets—like this site uses for the side menu—causes the menu links to load in a new window rather than the content frame specified in target. See the code for a work around; also see my Journal entry on 2/8/2006.
There wasn't much to the first example. Doing the reverse requires more code, because there can be more than one return value.
function handleResponseCityState() {
/* Call back for zip code lookup
* based on city and state
*/
if (httpReq.readyState == 4 || HTTPObj.readyState == "complete") {
document.getElementById("dlist").style.display = "none";
try {
if (httpReq.status == 200) {
var id = (HTTPObj.targetId) ? HTTPObj.targetId : HTTPTarget;
var zipField = document.getElementById(id).elements['zip'];
var data = HTTPObj.responseText;
if (data.length < 6) { //only one zip code
zipField.value = data;
} else {
processZipLookUp(HTTPObj.responseText, zipField);
}
} else (HTTPObj.readyState != "complete" {
/* This branch is only valid when used with the XMLHttprequest
* object. Then readyState will be a number. With iframes,
* IE triggers the onreadystatechange event before
* we're ready and would display this alert and then
* finish the process.
*/
alert(httpReq.status + ": " + httpReq.status.Text);
}
} catch (e) {
alert("Network error!\nPlease try later.");
}
}
}//eof handleResponseCityState
The highlighted line is where the extra code for handling more than one zip code starts. We're simply passing the data on to another function. This allows us to use exactly the same code used in iframe example, which is copied below.
function processZipLookUp(data){ /* Zip codes returned as comma delimited string */ var links = '', alist = data.split(','); var dlist = document.getElementById('dlist'); var opt; /* Add each zip entry */ dlist.options.length = 1; for (var i = 0; i <alist.length; i++) { opt = new Option(alist[i], alist[i], false, false); dlist.add(opt, null); } if ( i > 9) dlist.size = "10"; else dlist.size = "1"; /* Position menu */ var xy = getObjectUpperLeft(zipField); dlist.style.left = xy.x + 'px'; dlist.style.top = (xy.y + 5) + 'px'; dlist.style.display = "block"; dlist.focus(); toggleEscListener(true); } //eof processZipLookUp
When multiple zip codes are returned (think New York or Chicago) the code must provide for user selection. (I don't have nor would my Host provider allow me to install a database that includes the street address.) To handle this, four additional functions are included.
fillZip
function fillZip(e) { /* Put user zip code selection in field*/ e = e || event; var elem = e.target || e.srcElement; var zip = document.getElementById("exp2").elements['zip']; zip.value = elem.innerHTML; zip.focus(); document.getElementById("dlist").style.display = "none"; toggleEscListener(false); }//eof fillZip
getObjectUpperLeft
function getObjectUpperLeft(obj){ /* For positioning in reference to zip code field * Fix due to IE 7 requires that the fieldset be in a * positioned block (position: relative; will do) * Works with all browsers */ var x = obj.offsetLeft + (typeof obj.clientLeft != "undefined" ? obj.clientLeft : 0); var y = obj.offsetTop + obj.clientHeight + 2; return {x:x, y:y}; }//eof getObjectUpperLeft
listent4esc
function listen4esc(e) { /* Event handler to close zip code * menu on esc key with no selection */ e = e || event; keyCode = e.keyCode || e.charCode; if (keyCode == 27) { document.getElementById("dlist").style.display = "none"; toggleEscListener(false); } }//eof listen4esc
toggelEscListener
function toggleEscListener(on) { /* Bind and release event handler so * it is only running when menu is up */ if (browser.isMSIE) { if (on) document.body.onkeydown = listen4esc; else document.body.onkeydown = ""; } else { if (on) window.onkeypress = listen4esc; else window.onkeypress = ""; } }//eof toggleEscListener
This additional code is outside of our purpose here, demonstrating remote procedure calls using AJAX, so I won't elaborate on it. The code was needed so the example could work in a reasonable fashion, and I show the code to be complete. It is included in the download.