How to Create an Ajax Autocomplete Text Field: Part 5 [con't]
Variable Declarations
Here are the variables that we'll need to declare at the top of the script:
The isIE
variable is set to a boolean type according to whether or not the browser recognizes ActiveX objects. A value of true
would tell us that the browser is Internet Explorer since ActiveX is a proprietary Microsoft product. The completeTable
variable will store the dynamic table that contains the autocomplete list selections. The completeField
will hold a reference to the textbox.
The init()
Function
The init()
function is called from the document onload
event. This is where we set the global variables to their respective fields and define some default properties. We can call the DOM getElementById()
function to obtain a reference to the page elements. It uses the element's ID
attribute to return a reference to it. In the event that the same ID
attribute exists for multiple elements, only the first element is returned. A null
is returned if no matching element is found. The autorow
refers to the table row below the Autocomplete text field (the completeField
) and is used to determine the position of the list on the screen. This is done via the findPos()
function, which returns a coordinates object containing the top
and left
element properties. The menu
variable holds a reference to the <DIV> element containing the Autocomplete list table (the completeTable
). Setting its top
property to the top of the table autorow
positions the <DIV>
directly beneath the Autocomplete text field. Similarly, setting the left
property to the Autocomplete text field's left
property will align it vertically with the textbox. The completeField
textbox could have been used to determine the top
position as well but that would also require the additional step of adding its height. The completeTable
's visibility
property is set to "hidden" in order to hide the table until a character is entered in the text field:
The findPos()
Function
findPos()
function uses two alternate means of determining an element's position on the Web page:
The first approach uses the more common W3C DOM offsetLeft
and offsetTop
properties. These give the position of the element relative to an arbitrary ancestor node, known as the offsetParent
. I say arbitrary because unfortunately, different browsers and even different versions of the same browser give a different offsetParent
. As a case in point, as of Microsoft Internet Explorer 5, the offsetParent
property returns the <TABLE>
object for the <TD>
tag, whereas in Internet Explorer 4.0 it returned the <TR>
object. This can get a bit complicated. The solution is the while loop
that iterates through the the offsetParents
. Here's how it works: the offsetParent
will also have the offsetTop
and offsetLeft
properties, giving its position relative to its offsetParent
, and so the cycle continues, until the offsetParent
is the topmost node in the document, which will always be at offset (0,0).
The second uses the simple, but practically obsolete Netscape-compatible x
and y
properties.
Both the left
and top
are returned as an Object literal. This allows us to retrieve the properties using the Left
and Top
member property accessors:
The autoComplete()
Function
Every time a character is entered in the Autocomplete field, the autoComplete()
function makes a call to the server to update the list. The first part of the if
statement weeds out strings that don't require a server call. To keep our example simple, the code checks for a blank string rather than parse it for valid characters. One of the effects of this code is that the clearTable()
function is called whenever the field is cleared using the "Backspace" button. If there's a search string in the field, the script passes its value to the AutocompleteServlet
in the url
query string, creates a new AJAX object and sends the search string using a GET
call. Formulating the URL string may present a bit of a hurdle for those unfamiliar with AJAX and Servlets so let's go over what's required to construct it.
In general, an URL has this form: protocol//host:port/pathname#hash?search
. We're using a relative link, so we really only need to provide the name of the page , rather than the full path. Servlets are different from most Web pages in that its properties are configured in the deployment descriptor web.xml
file. One of the properties that's in the web.xml
file is the name of the servlet class. Hence, you are essentially calling the servlet by its alias and not by the proper servlet name ("autocomplete," in our case). The search
portion of the URL is made up of name/value pairs, separated by ampersands (&). On the server, these parameters are passed on to the servlet. We're sending two name/value pairs: the action
and the searchString
. The action
is the request property that tells the servlet what we'd like it to do, much like ordering a meal from a menu. A value of "complete" is a request to provide a list of matching items that will be used to populate the Autocomplete list. The searchString
is the partial symbol or fund name that we want to complete. Unlike the action
parameter, which never changes, the searchString
is updated after every character.
The result of the initRequest()
function — an AJAX object — is stored in the req
variable. The XMLHttpRequest
object's onreadystate
property must have a function assigned to it in order to process results of the AJAX call, once it returns from the server. There are four readyState
s, but the last one (#4) is by far the most used, since it's the only one where all the results have been returned. Here's the list of readyState
values and their meaning:
- The request is uninitialized (before you've called
open()
). - The request is set up, but not sent (before you've called
send()
). - The request was sent and is in process (you can usually get content headers from the response at this point).
- The request is in process; often some partial data is available from the response, but the server isn't finished with its response.
- The response is complete; you can get the server's response and use it.
The status
property is the HTTP status header. There are many possible
values, but only two interest us: 200 - OK
and 204 - No content
.
A status of 200 is the green light to go ahead and call the parseMessages()
function. A status of 204 means that nothing was returned in response to our
request, so we clear the list table, in case there was something in it previously.
Now that we've set the callback function to handle the AJAX object's response,
we can proceed to send the request to the server. The open()
function establishes
the connection to the server and send()
makes the actual call. The send()
function's
parameter is only used for uploads, so we can set it to null
. For more
information on AJAX calls, see my URPMs: AJAX Edition article.
The initRequest()
Function
The initRequest()
function is a basic "boilerplate" implementation
of an AJAX Request object initialization. Internet Explorer uses ActiveX objects
for AJAX calls. In all other browsers, we have to create a new XMLHttpRequest
object:
The parseMessages()
Function
The parseMessages()
function is responsible for processing the AJAX response and populating the list. To be sure that we're starting with and empty list every time, we call clearTable()
. The first if
statement checks to see if anything was returned from the server. While the previous check for a return status of 204 should eliminate empty responses, it's possible to have an empty string returned. We can test for an empty string by using the String class's length
property. Since any non-zero value would evaluate to true, the exclamation point (!) in front of the length
property would cause the function to exit on a zero length string.
The RegExp test()
function checks for quotes around the responseText
,
indicating an error encountered in the servlet. Recall that we return
JSONException
messages from the AutocompleteServlet
as a quote-delimited
string. The String class's built-in replace()
function is used to remove the
quotes, so we can display the error message in an alert
box. I returned
it as a string for simplicity, but a more complex error object, such as one
that contains an error number and level would work well as a JSONObject
.
The string can be displayed in a alert
box directly. Originally, the
intent was to use the eval()
function on the responseText
, but security
concerns with the use of the eval()
function within AJAX applications led me
to abandon this approach in favor of this safer alternative.
Same goes for the list items, which are returned as an Array of JSONObjects
.
Rather than using the eval()
function to create the JSONArray
, we can
use one of the many free parsers available. The one I used throws a SyntaxError
exception, so we have to make the function call within a try/catch
block.
A for
loop is used to iterate through each item and add it to the list,
in the appendFund()
function.
Finally, the completeTable
's visibility
property is set to true
to display the items: