How to Create an Ajax Autocomplete Text Field - Part 8 / Page 2 | WebReference

How to Create an Ajax Autocomplete Text Field - Part 8 / Page 2

[previous] [next]

How to Create an Ajax Autocomplete Text Field: Part 8 [con't]

The Fix Is In

With a little ingenuity, each of these issues can be remedied. To compensate for the scrollbar, we can manually increase the width of the table. Add the following code to the bottom of the parseMessages() function, where we set the container's visibility property to "visible."

Add the following code for the setListWidth() function to the JavaScript:

The RegExp test() function determines whether or not we even want a scrollbar by checking for the scrollbar class in the container's className property. A ternary operator (expression ? true : false) then evaluates the table's clientHeight against the container's. Without a scrollbar, they're the same, but when the vertical scrollbar is present, the completeTable's exceeds the container's by the hidden rows' combined height. We can then compensate for the scrollbar then by adding its width to the container's style.width property. The scrollbar is 16 pixels wide in most browsers, but not always. In Safari running on Windows 2000, it's only 15 pixels. The false part of the ternary operator sets the container's clientWidth to the table's value. At first glance, this code might seem redundant, since it would already be that size by default. Indeed it is, except when we've already set it. Chances are that the scrollbar will appear after the first letter, but won't be required as you type in more characters. Therefore, the scrollbar would no longer be displayed, freeing up the 16 pixels. It's best to remove them.

Our next issue is the list height in Internet Explorer. This is somewhat easier to fix than the previous one, since it only affects the one browser. It turns out that while Internet Explorer doesn't recognize the max-height property, it allows us to set the height to an expression. CSS expressions were introduced in Internet Explorer 5.0 and they allow you to to assign a JavaScript expression to a CSS property. To set the maximum height for the completeTable, change the height property in the div#container rule to the following:

In order of execution, the expression:

  1. retrieves the completeTable element using the DOM getElementById(String elementID) function.
  2. converts its clientHeight property to an integer, so that we may compare it to a value of 100.
  3. tests to see if it exceeds 100, using a ternary operator (expression ? true : false).
  4. sets the height property to 100 pixels if it evaluates to true.
  5. sets the height to the completeTable's current clientHeight if it evaluates to false.

A word to the wise, be careful when writing expressions because errors aren't handled in the same as in regular JavaScript. A runtime error can even cause the AutocompleteControl to not appear at all! It's a good thing that this particular expression is a standard workaround.

The last issue is the most problematic to deal with. If we knew the row height in advance, we could just multiply that by the number of rows that we want visible. Unfortunately, the row height is largely determined by the <TD> padding property, so we're out of luck there. The behavior stems from the setting of the list height in pixels rather than rows. Webmasters who want to use our control in their Web pages shouldn't have to enter pixels in a CSS file, anyway. What they want is to include the control in their page and set the list's size to the number of desired rows to display, such as "10."

The <jsp:param> Element Revisited

As mentioned in last week's article, when using the <jsp:include> tag to insert a dynamic resource in a page, the request is sent to the included resource, the included page is executed and the result is included in the response from the calling JSP page. The <jsp:include> tag provides a means to pass name/value pairs to the included resource via the <jsp:param> element. In the included resource, you can retrieve the parameters by using the getParameter(string paramName) method of the request object. Used in conjunction with the <%= %> output shorthand, you can display the variable or use it in a client-side script. Therefore, we can add a <jsp:param> element in the AutocompleteSearchCSS.jsp page to set the list size:

In the AutocompleteControl.jsp file, we add the following line to convert the parameter to a JavaScript variable:

Back in our AutocompleteList.js file, we add some code to set the list height and maxHeight properties.

The parseMessages() Function

We can't set the list height in the init() function with the other variables because we need the row height to calculate the container <DIV>'s maxHeight. We'll have the information we need after the rows have been added to the table, in the parseMessages() function:

The purpose of the setListHeight() function is to calculate the list's maxHeight property based on the supplied actualListSize parameter. The outermost if statement achieves two goals. First, it ascertains that there was a listSize variable supplied via a parameter within the AutocompleteControl's tag. That tells us that the user wants a scrollbar to appear if the number of items in the list exceeds that value. Second, it only accepts numeric values because the isNaN() function returns true if the listSize is "Not a Number." Hence the exclamation in front of the isNaN() function returns true if it IS a number. Since the listSize is a user property it's a good idea to validate it. The desired containerHeight in pixels can then be calculated based on the actualListSize and the desired listSize.

In DOM-compliant browsers we can set the maxHeight property to the containerHeight directly. Internet Explorer 5/6 require that we apply the setExpression(property, expression, language) function to the height property since they don't support the maxHeight property. The isMaxHeightSupported global variable specifically targets IE 7+ browsers (which do support maxHeight) so we don't use the expressions on them. The third argument is optional, but we should supply it because the default is JScript. The setExpression() function also accepts VBScript code.

The Autocomplete.css file contains a rule called div.scrollbar to add the scrollbar to the list automatically when required. We add it to any preexisting classes by using the "+=" operator.

The last line in the function sets itself to an empty one that does nothing because we only have to set the maxHeight and className once. Rather than use an if statement every time the parseMessages() function executes, we call the function every time, but nothing will happen after the first run through.

Here's the code for the setListHeight() function:

Testing for max-height Support

Although newer browsers recognize the max-height property, there are still enough Internet Explorer 6 browsers out there to warrant supporting the height expression approach. Not being a supporter of browser sniffing, I prefer to test on a case by case basis, such as container.setExpression in the previous code snippet. It's ultimately a lot easier than checking browser types and versions. Having said that, this technique doesn't work with CSS styles because the scripting engine doesn't verify that the property exists. For this reason, you can set the maxHeight to anything you want and even retrieve the value later without errors, but the property won't affect the displaying of the element in any way!

What we can do is apply the style to a test element and test to see that it yields the expected result. To check for the maxHeight property, we have to mimic the list <DIV> element and include another one within it that's larger. That's what the testForMaxHeightSupport() function does. Here's how it works:

In the CSS file, we add an ID rule that contains the max-height and overflow properties. The <DIV> is made invisible and given absolute positioning so that it won't move any of the other page elements:

The isMaxHeightSupported variable needs to be added to the other global variables at the top of the script:

[previous] [next]