NOTE: Some of this article comes from "Remote Scripting with IFRAME" by Eric Costello and Apple Developer Connection; 02/08/2002. It can be found here: http://www.oreillynet.com/pub/a/javascript/2002/02/08/iframe.html.

Forward

In the above-mentioned article, Costello says that "Remote Scripting is the process by which a client-side application running in the browser and a server-side application can exchange data without reloading the page. Remote scripting allows you to create complex DHTML interfaces which interact seamlessly with your server."
Often, Remote Scripting is asscociated with Microsoft and ASP or with a Java Applet. I wanted to avoid both, so this is my path. (There is also a popular implementation here.)
One of my computer science teachers in college was a retired IBM software engineer. To hear him tell it, when not colating punch cards or replacing vacuum tubes, he wrote operating system code in 1s and 0s. And now that he was a teacher of Java, he laughed at some of the "modern benefits and advances", such as code reuse, touted by the object oriented crowd.
"We had code reuse," he would say. "It was called 'copy and paste'."
In that spirit, I think most programmers would agree that using and incorporating other people's code, where and when allowed, is a perfectly acceptable and often helpful practice. Indeed, it's at the heart of open source in general and community knowledge websites like this one. So much of what I'll be demonstrating in this article is not of my own creation; the brainpower belongs to others. My job, as is often the case for web developers, was merely to glue the pieces together, and to "get it working."

The Project

Specifically, what needed to get working was a project at my work that involved porting a desktop application created in Microsoft Access to a web-based PHP/MySql intranet application. Among the directives from management was to make the web application look and behave as much like the existing Access application as possible. And one of the many small features of this application which didn't map precisely to the web platform was a self-populating drop down search field. You've certainly seen these in various applications: the user begins to type in a text box, and as they do, the drop down box is populated with a list (usually alphabetically ordered), starting with the string that is being typed. As each letter is typed, the list changes and (usually) becomes more narrowly focused.

To recreate this in a web page I had several options. First, I could build the Select element server side with PHP, populating the Option elements from the database, and send the page with the pre-populated list. I could then tie into that option list with some Javascript that searched the list and selected the appropriate option based on the string typed into the text box. Fairly straightforward, except that the option list I needed to create had more than 60,000 records and was growing by a couple thousand every month. In fact, a previous developer had prototyped this method in ASP. The resulting page sent to the browser, with it's 60,000 option-long Select list, was nearly 4 MB. And the Javascript that searched the list with each "onkeyup" in the textbox was extremely slow, about 5-10 seconds between each keypress, or 30-seconds to type "Smi" and point to the "Smith" records.

Perhaps the Javascript could have been optimized somewhat, but to have a 4 MB page being requested concurrently, and often, by 10-plus users obviously wouldn't work. So a second option I had was to omit the Select list and just make the textbox a standard search field. Type in "Smi", hit enter, and get back a list of all the "Smi*" records. A reasonable approach, but I felt it didn't meet the "emulate Access" directive closely enough.

So finally, to the subject of the article. I thought, if I could talk to the server from the client, somehow without reloading the current page, I could "fake" the dynamic drop down list. Searching the net, I found several Remote Scripting options: several Java applets, a very nice and well-used Javascript library from Brent Ashley http://www.ashleyit.com/rs/main.htm, and other methods. But I settled on the Iframe approach referenced at the beginning of this article for it's simplicity and lightweight implementation.

The Specifics

You can read Costello's article to get the basics of the technique, but what follows are the specifics from my case.
First, I created my small HTML form:
<form method="POST" id="searchForm" name="searchForm" action="<?=$_SERVER['PHP_SELF']?>">
Search by Last Name: <input type="text" id="nameSearch" name="nameSearch" onkeyup="doSearch(this.form);" 
size="20" value="<?=$nameSearch?>">
 
<span id="spanSelect">
<select name="id" id="searchSelect">
<option value="-1">Select</option><?=$searchOptions;?></select></span>
<input id="searchButton" type="submit" value="Go" onclick="return submitSearchCheck('searchSelect');" disabled>
<input type="hidden" id="listAsStrng" name="listAsStrng" value="<?=$listAsString;?>">
</form>
I added the reference to the external Javascript file holding the IFrame (and other) code.
<script language="javascript" src="./js/searchForm.js"></script>
Next, I added Eric Costello's IFrame code to the searchForm.js file:

<?php

var IFrameObj
// our IFrame object - global
function callToServer(term
{
    if (!
document.createElement) {return true};
    var 
IFrameDoc;
//var URL = 'server.html' + buildQueryString(theFormName);
    
var URL './server.php?s='+term;
    if (!
IFrameObj && document.createElement
    {
    
// create the IFrame and assign a reference to the
    // object to our global variable IFrameObj.
    // this will only happen the first time 
    // callToServer() is called
        
try 
        {
            var 
tempIFrame=document.createElement('iframe');
            
tempIFrame.setAttribute('id','RSIFrame');
            
tempIFrame.style.border='0px';
            
tempIFrame.style.width='0px';
            
tempIFrame.style.height='0px';
            
IFrameObj document.body.appendChild(tempIFrame);
      
            if (
document.frames
            {
            
// this is for IE5 Mac, because it will only
            // allow access to the document object
            // of the IFrame if we access it through
            // the document.frames array
                
IFrameObj document.frames['RSIFrame'];
            }
        } 
        catch(
exception
        {
        
// This is for IE5 PC, which does not allow dynamic creation
        // and manipulation of an iframe object. Instead, we'll fake
        // it up by creating our own objects.
            
iframeHTML='<iframe id="RSIFrame" style="';
            
iframeHTML+='border:0px;';
            
iframeHTML+='width:0px;';
            
iframeHTML+='height:0px;';
            
iframeHTML+='"></iframe>';
            
document.body.innerHTML+=iframeHTML;
            
IFrameObj = new Object();
            
IFrameObj.document = new Object();
            
IFrameObj.document.location = new Object();
            
IFrameObj.document.location.iframe document.getElementById('RSIFrame');
            
IFrameObj.document.location.replace = function(location
            {
                
this.iframe.src location;
            }
        }
    }
  
    if (
navigator.userAgent.indexOf('Gecko') !=-&& !IFrameObj.contentDocument
    {
    
// we have to give NS6 a fraction of a second
    // to recognize the new IFrame
        
setTimeout('callToServer()',10);
        return 
false;
    }
  
    if (
IFrameObj.contentDocument
    {
    
// For NS6
        
IFrameDoc IFrameObj.contentDocument
    } 
    else if (
IFrameObj.contentWindow
    {
    
// For IE5.5 and IE6
        
IFrameDoc IFrameObj.contentWindow.document;
    } 
    else if (
IFrameObj.document
    {
    
// For IE5
        
IFrameDoc IFrameObj.document;
    } 
    else 
    {
        return 
true;
    }
  
    
IFrameDoc.location.replace(URL);
    return 
false;
}

?>
This code also includes a Javascript function called "buildQueryString" to, obviously, build a query string, but since I only needed to pass a single search term I omitted that function, and changed this line:
var URL = 'server.html' + buildQueryString(theFormName);

...to this line:
var URL = './server.php?s='+term;
I also added the "term" parameter to the callToServer() function.
Now, each time a user makes a keystroke in the "nameSearch" textbox, the doSearch() Javascript function is called. That function looks like this:

<?php

function doSearch(theForm
{
    var 
searchTerm theForm.elements['nameSearch'].value;
    if(
searchTerm.length 2
    {
        return;
    }
    var 
s="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ -.";
    if(
s.indexOf(searchTerm.charAt(searchTerm.length-1)) == -1
    {
        return;
    }

    
callToServer(searchTerm);  
}

?>
Here, the search value is extracted from the textbox, and if it's less than 2 characters in length, nothing is done, and we are returned. With 60,000 records I needed to limit the search results and the resulting drop down list, so I implemented this arbitrary length check. Next, I check that the character entered is alphanumeric, a space, dash, or period. Again, an arbitrary limit that is specific to my case. Finally, if the search term is >= 2 characters and conatins valid characters, we dispatch the search term to callToServer().
Basically, the callToServer() function creates a hidden IFrame object and "calls" the server by loading the URL and it's query string with the IFrame's location attribute. There are also various browser compatibility and error checks interspersed.
Now, over on the server side is our PHP page of course, in my case server.php. The basic code looks like this:

<?php
if(!isset($_GET['s']))
{
    
$listAsString 'No results';
}
else 
{
//cleanVar() just does some slash/trim/strip_tags type things
    
$listAsString searchFromJs(cleanVar($_GET['s']));
}

function 
searchFromJs($searchTerm
{
    
    
$query 'select id, first_name, last_name 
        from users where last_name like "'
.$searchTerm.'%" 
    order by last_name, first_name";        
    $result = mysql_query($query);
    if($result === false) return '
Invalid query' . mysql_error();  
    if(mysql_num_rows($result) < 1) return '
No results';
    while($r = mysql_fetch_assoc($result))
    {
        $list .= $r['
id'].'~'.$r['first_name'].'~'.$r['last_name'].'|';
    }
    $list = htmlspecialchars(preg_replace('
/|$/','',$list),ENT_QUOTES);
    return $list;    
}
?>

<html>
<script type="text/javascript">
window.parent.handleResponse(encodeURIComponent('
<?=$listAsString;?>'));
</script>
</html>
This should be pretty straightforward. I check for the search term, and call the searchFromJs() funciton if needed. In the searchFromJs() function, I'm merely building a string containing the database records. The records are separated by pipes: "|", and the fields are delimited by tildes: "~". I clean up the string a bit and return it.
The HTML portion of the page is nothing but basic Javascript, so that when the page is loaded (remember, it's a hidden IFrame), it calls the handleResponse() Javascript function in it's parent page (search.php). In the process, it encodes the string. (Not doing this was actually an undiscovered bug in my application, causing the string to be truncated in certain cases.)
Let's look at that handleResponse() function now. It looks like this:

<?php

function handleResponse(str
{
    var 
theString decodeURIComponent(str);

    if(
theString == 'No Results' || theString.indexOf('~') == -1
    {
        return;
    }
    var 
= new getObj('listAsStrng');
    
l.obj.value str;
   
    var list = 
arrayFromString(theString,'|');
    var 
opts '';
  
    for(
i=0< list.lengthi++)
    {
        
rec = Array();
        
rec = list[i].split('~');
        
opts opts '<option value="' rec[0] +'">' rec[1] + ' ' rec[2];
    }
    var 
sp = new getObj('spanSelect');
    
sp.obj.innerHTML '<select name="names" id="searchSelect">'+opts+'</select>';
    var 
= new getObj('searchButton');
    
b.obj.disabled false;
}

?>
The function takes the string created and passed by searchServer.php, decodes it, tests it for validity, and transforms it to an array using this Javascript:

<?php
function arrayFromStringsdelim )
{
    var 
= (delim == null) ? '|' delim;
    return 
s.split(d);
}

?>
Back to handleResponse(), the array of names is next transformed into Option tags, inserted into a Select List, and the list placed in the HTML document using innerHTML. (The getOBJ() function merely uses getElementById() to fetch the specified element.)
And that is it. The user now has a dynamic drop down list wherein they can find and select the appropriate value for futher processing. Amazingly, even though it is done on every keystroke (after 2), it is surprisingly fast. Also, with 60,000+ records, even a subset of records can be quite large, but I've run into no problems with the string being too large to pass through Javascript.
As a couple of additional notes, once the user selects a value from the list, clicks the submit button and the page is reloaded, the search term and the Select List are de-populated. To get around this, I added the hidden element "listAsStrng" to the form, and set its value in handleResponse() here:

<?php

var = new getObj('listAsStrng');
l.obj.value str;

?>
Now, when the form is submitted, and the page reloaded, the search textbox () and the select list () can be repopulated with their values, maintaining state for the user.
Also, it's possible, if you can track down the source code (remember our principle of copy and paste?), to remove the text box and implement the Select list as a combination editable text box and drop down list. It's a bit hairy and browser specific, so I'll leave this as an exercise for the reader.
So, we now have a fairly fluid implementation of a dynamic drop down using PHP, JavaScript, Remote Scripting, and a bit of copy and paste. Enjoy.