In "Part 7: Moving to a CSS Design",
we updated the control's layout to CSS and decoupled it from the Autocomplete.jsp
page in order to make it into a standalone Web control. Then, in Part 8, we
added a scrollbar to the list using a <jsp:param>
tag to pass the list
size in rows. This week we continue to add functionality and tweak the
appearance of our control by implementing search string matching in the list
items and by adding a CSS drop shadow effect. Next time, we'll be adding support
for the arrow and control keys.
The highlightClass
Parameter
Like the scrollbarColorClass
parameter before it, the highlightClass
allows the user to specify a CSS class that will control the appearance for
search string match highlighting. The highlighting feature can easily be turned
off by supplying an empty string for the parameter value. Here's the bottom
part of the Autocomplete control <jsp:include>
tag contents, on the AutocompleteSearchCSS.jsp
page:
AutocompleteControl.jsp
page, a variable by the same name (highlightClass
),
will store the parameter for later use:
Tracking the searchString
The searchString
is used in the autoComplete()
function where it's
submitted via an Ajax call to the server. The search string highlighting happens
later, after the Ajax onreadystatechange
event fires with a readyState
of 4. Since this is done asynchronously, passing the string to the callback
function is a risky proposition. Therefore, we should bind it to the event.
In fact, it's always a good idea to bind any objects or variables that are
required in the callback function because of the potential time lag. In a previous
article, entitled Universally Related Popup Menus with JavaScript: Ajax Edition, I introduced the bind()
function:
The
bind()
function ... is used to provide the [callback] function with the objects and variables that it needs when theXMLHttpRequest
fires theonreadystatechange
event. It does so by creating local variables within the function on which it's applied. The function thatbind()
is being applied to ... is saved in the method variable. The first argument is the object which will define the scope of the callback function ... All the other arguments will be passed along to the calling function, but in order to do that, we have to separate them from the first argument. The Array object has the perfect method calledslice()
. It splits the array from the element number that we provide and returns the remaining elements as a newArray
. Unfortunately, it turns out that the arguments property is not a trueArray
. Although it shares some of the same properties such as length, it does not implement all the methods of a trueArray
object. There are two ways around this: we write our ownslice()
function, or, we steal the existing one from theArray
object! You know what they say about reinventing the wheel. Reuse! Once we've copied theslice()
function over to the arguments property, we can call it just as if it always existed. The return type is a function because theonreadystatechange
property expects one:
Adding the bind()
function to the Function
object's prototype
property
allows us to call it from any function using the dot notation:
Hence, we can update our code in the autoComplete(Event)
function to bind the
searchString
to our anonymous function. The bind()
function will set
the req
variable as the "this
" object and pass the searchString
to the anonymous function to be used later. Even though it's absent from the
function's arguments list in the signature, it will available to the function
through a JavaScript scoping phenomenon known as closures. The way it
works is that by returning a function instead of an actual value, the scripting
engine keeps track of the variable rather than destroy it as would usually
occur. The reason is simple: the garbage collection only cleans up variables
that are no longer in use. Since the returned function hasn't executed yet,
passed parameters remain in memory. The searchString
can then be passed
directly to the parseMessages()
function because there's no discernible time
lag in execution between the anonymous function and parseMessages()
:
The following code snippet demonstrates how we add the searchString
argument to the parseMessage()
function's signature and alter the loop to send
the searchString
to the appendFund()
function, where it will finally
be used. The JavaScript 1.3 call()
function is applied to appendFund()
to set
the current jsonObject
array element as the this
object and send
the searchString
as the function argument. If you think that the call()
function closely resembles bind()
, you're right! The bind() function is based
on call()
, except that it returns the bound function, rather than the function
results:
The appendFund()
Function
The appendFund()
function's one parameter was the JSON Fund
object, which can be replaced with the searchString
, since the fund is the this
object. A new function called highlightSearchString()
will handle the highlighting
on the fund symbol and name:
The highlightSearchString()
Function
The highlightSearchString()
function is added to the String object much like
bind()
was added to the Function object, so all strings will share it.
The function accepts three parameters: the table cell
containing
the text, the searchString
that we are matching against, and the CSS
highlightClass
rule that governs the appearance of the highlighted text.
The string that contains the searchString
is the this
object.
As we saw in the code snippet above, the highlightSearchString()
function is
called every time we add the fund
field to the list. Therefore, the highlight
formatting may or may not be applied. That's why it checks for the highlightClass
parameter. If there isn't one to apply, then the full searchString
is
returned for displaying.
To match the searchString
, we'll use a regular expression. The RegExp
constructor takes two parameters: the pattern and modifiers. The caret (^
) symbol
tells it to match from the start of the test string. The "i
" indicates
that we want the matching to be case insensitive. The String's match()
function
takes our regular expression pattern and applies it against the test string,
which is contained in the this
object and returns the matching portion.
If a match is found, we can create a <span>
element to enclose the highlighted
text. The text is included as a child of "text node" type to the span,
which is then appended to the cell. Finally, the RegExp.rightContext
property is returned to the calling function. It contains the remaining text
after the matched portion. This text will also be appended to the cell as a
text node in the appendFund()
function:
Here is a sample of a typical table row generated by the appendFund()
and highlightSearchString()
functions above: