17th February 2006

Posted in

Blog :: Ajax Autocomplete With Scriptaculous

Recently I was using the masterful script.aculo.us javascript library to create autocomplete psuedo-select boxes (look here to see what I'm talking about if you haven't seen this) and came up with a simple solution to what I expect is a common problem. Autocomplete boxes (I've heard them called "google suggest" style boxes) are frequently used for searching - the near instant feedback of seeing a list of results adjust as you type can be very useful in some instances. I intend, for example, to roll out a autocomplete search box for this blog. From a end user perspective, the active search of past entries is certainly better than my current "Next Page" style links. Even a normal search box might not tend to be used: having to submit and navigate away from the current discourages the casual browser...

Using scriptaculous' built in Autocomplete functionality is certainly easy: basically all you need to do is load the libraries, create a text box and a div in which to display the results, and then include a javascript line linking the text box, div, and the list coming from the server. The list is simply a dynamically generated UL. Scriptaculous makes an AJAX connection back to the server url of your choice, posting the value that's been typed so far in the text box, and displaying the list the server returns below the text box. You can navigate through the list with arrow keys and the enter button or click on an item and the selected item's value will be placed in the text box.

So far this provides basically a better select box for large data sets. The built in select box elements break down as usable interfaces when the option list gets too long - for me this is somewhere around 50 elements or so. You can type in a normal select box to jump through the list, but only the first letter is used - if you have more than 3 or 4 options per letter than you still end up having to scroll extensively. Select boxes do, however, let you link a key (say a database unique ID number) with a value that the end user selects. If you have an option like <option value="4" selected>metapundit</option> in a select in a form and you submit the form, the script receives the "4" instead of the textual value the user saw.

This is useful functionality that is easy to replicate with the scriptaculous autocompleter. The server can add an additional piece of information to the UL it returns by adding an id to each LI. In this case I choose to set the id to auto_$key. (Purely numeric identifiers aren't allowed IIRC). I take advantage of this piece of information by adding a callback function as provided for by the Autocompleter initialisation call (see the docs for example). Now my javascript init line looks like:

new Ajax.Autocompleter("autocomplete", 
"/bluetest/test_server.php", {paramName: "q",minChars: 1, 
afterUpdateElement: updateHidden });

The "afterUpdateElement" option contains the function that will be called after the text box has been updated. The function will be passed two items: the text box being updated and the li that was selected. With these two pieces of information I can update a hidden element with the id of the LI like so:

function updateHidden(txt, li)
    hiddenName = txt.name.replace("_","");
    id = li.id.replace("auto_","");
    txt.form[hiddenName].value = id;

This function assumes that the text element name will be prefixed with an undercore and that a hidden element exists with the same name minus the underscore. Additionally it assumes that you've followed the convention I outlined above and set the id's of the LI's with the key you want prefixed by "auto_". Normally, of course, the list of values and id's would be drawn from a database, but for my demo I simply loop through an array and use the indexes of the values for my key. The user picks a color from the drop down list and when the form is submitted, the index of the color as well as the color name is passed along. There are of course holes in the validation of this data (pick a color from the list, then manually type something else and hit submit), but it's a start. I'm trying to fit this into my library of scriptaculous enhanced QuickForm controls, so my next project is to wrap this in a quickform control that client validates whether or not text value matches the id of hidden value...

You can see it all tied together at the sample page which links to the demo, the server (to see the output being generated), and the source of the server. View source at the demo page to see the HTML, CSS, and Javascript of the client (the only php in the client is to dump the $_POST array and print a random number).

Update: Another semi-useful tidbit. I decided to use the scriptaculous Autocompleter for a situation where I wanted a user to type an url in a textbox (absolute with no domain: like /foo/mysection/mypage). Because of space constraints I wanted to show the list of urls with the part the user had already typed removed (so in the previous example if the user had already typed /foo/mysection the list might only show /mypage, /mypage1, etc). Of course selecting one of these options needs to put the entire path into the text box. So now instead of trying to update a hidden field with a different value than is displayed on the list, I need to put a different value than is displayed on the list into the textbox itself. Fortunately the documentation describes how to do something similar to this. In the LI's generated by the server, any text wrapped in a <span class="informal"> tag is displayed by not placed into the textbox. This plus a little css gets me there: my server can now output a list like

<li><span class="informal">mypage1</span><div style="display:none">/foo/mysection/mypage1</div></li>

The value in the span (mypage1) is what is displayed in the list; wrapping the real value in a display:none div hides it from view. Very sensibly then, scriptaculous strips the html from the value it places into the text box and I get the complete url "/foo/mysection/mypage1" placed in the text box when its LI is selected. Voila!

Posted on February 17th 2006, 01:35 PM