Here are two examples of an RPC using an iframe to retrieve information from a database on the server. Clicking the button labeled "Find" sends the data (either zip code or city and state respectively) entered to the server-side script as the query or search part of the URL of the iframe.
On the server a remote procedure looks up the corresponding data in a database and returns it in a page to the iframe. That page and the main page contain code to fill in the respective fields. This is a better example of remote scripting than the arithmetic example since it accesses a large database—well, at least too large to download.
Try it out. Enter a zip code (or city and state) and then click the appropriate button. If at first you don't get the desired result, try again. The database is old and missing some codes so you may have to try a couple.
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 would be slow.
Only two pieces of code presented in the explanation are modified.
Examine the code that has been changed starting with the example looking up city and state based on zip code.
Start with the page returned to the iframe.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Title</title> <script type="text/javascript"> function returnCall() { var handle = null; if (parent!=self) { if (this.frameElement) { handle = this.frameElement; } else if (parent.document.getElementById("iframe")) { handle = parent.document.getElementById("iframe"); } else { alert("Out of luck"); } if (handle) { handle.callBack("{city: 'Kokamo', state: 'MI'}"); } } } window.onload = returnCall; </script> </head> <body> <div id="data-wrapper"> <div id='Kokamo'> </div>"; <div id='MI'> </div>"; </div> </body> </html>
A PHP script looks up the city and state based on zip code and generates the return html. Ignore the contents of the body section. That supports using DOM methods to extract the return data, but the example has been changed to use the JSON data format. The result is passed as a string argument to callBack.
JSON is a string, "{city:'Kokamo', state:'MI'}", that looks like a Javascript literal for an object. We'll see how this is converted into a JavaScript object in a moment. Now checkout the handler.
function handleResponseZip(){ /* This clears a listbox if the user hasn't */ document.getElementById("dlist").style.display = "none"; if (HTTPObj.readyState == 4 || HTTPObj.readyState == "complete") { if (HTTPObj.status == 200) { var id = (HTTPObj.targetId) ? HTTPObj.targetId : HTTPTarget; var targ = document.getElementById(id); eval("var data = " + HTTPObj.responseText); targ.elements['city'].value = data['city']; targ.elements['state'].value = data['state']; } else if (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 so the test prevents showing the alert * when using iframes and IE. */ alert(HTTPObj.status + ": " + HTTPObj.status.Text); } } } //eof handleResponseZip
This line, eval("var data = " + HTTPObj.responseText), converts the returned string into a JavaScript object. The for-name-in construct can be utilized to process the data; although, in IE there is an incompatibility with frames so I didn't use that.
See the Journal entry 2/8/2006 for more details on incompatibilities with IE.
The page returned when finding the zip code for a specific city is similar to the page returned for finding a city based on zip code.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Title</title> <script type="text/javascript"> function returnCall() { var handle = null; if (parent!=self) { if (this.frameElement) { handle = this.frameElement; } else if (parent.document.getElementById("iframe")) { handle = parent.document.getElementById("iframe"); } else { alert("Out of luck"); } if (handle) { handle.callBack("60012,60013,60014"); } } } window.onload = returnCall; </script> </head> <body> <div id="data-wrapper"> </div> </body> </html>
The data is returned as a comma delimited string or a single zip code.
The response handler code is much more complicated than previous examples because it potentially has to respond to more complex issues.
function handleResponseCityState(){ /* Example 2b: Lookup zip code based on city and state. * 1. Handle 1 or no zip codes * 2. Make popup for multiple zip codes * with zip code menu (dl-dt-dd) */ if (HTTPObj.readyState == 4 || HTTPObj.readyState == "complete") { if (HTTPObj.status == 200) { var data = HTTPObj.responseText; var zipField = document.getElementById(HTTPObj.targetId).elements['zip']; var dlist = document.getElementById("dlist"); if (data.length < 6) { zipField.value = data; dlist.style.display = "none"; } else { processZipLookUp(data, zipField); } } else { //handler error } } } //eof handleResponseCityState
As you can see, the handleResponseCityState is no different than the previous example when a single zip code or error is returned. However, 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.
Code to fill zipcode field allowing for the possible need to let the user select from a list.
function processZipLookUp(data, zipField) { /* Zip codes returned as comma delimited string */ var links = '', alist = data.split(','); var dlist = document.getElementById('dlist'); var opt; /* remove previous enteries */ dlist.options.length = 1; /* Add each zip entry */ for (var i = 0; i <alist.length; i++) { opt = new Option(alist[i], alist[i], false, false); if (browser.MSIE) dlist.add(opt, i + 1); else dlist.add(opt, null); } if ( i > 9) dlist.size = "10"; else dlist.size = ++i; /* 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 function fillZip(opt) { /* Put user zip code selection in field*/ var zip = document.getElementById("exp2").elements['zip']; if (opt.nodeName.toLowerCase() == "select") zip.value = opt.options[opt.selecctedIndex].text; else zip.value = opt.text; zip.focus(); document.getElementById("dlist").style.display = "none"; toggleEscListener(false); }//eof fillZip
Code to position Zip code pick list.
function getObjectUpperLeft(obj){ /* For postioning 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
Code to listent for ESC key to close pick list not selecting a zip code.
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
Code to toggel ESC kye Listener on and off.
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 scope, demonstrating remote procedure calls using iframes. The code was needed so the example could work in a reasonable fashion, and is shown to be complete. It is included in the rpc-handlers.js.