Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax In Action (2006).pdf
Скачиваний:
63
Добавлен:
17.08.2013
Размер:
8.36 Mб
Скачать

406CHAPTER 10

Type-ahead suggest

Because we want to focus solely on the Ajax mechanisms being put in place, we’ll just cover much of the content here at a high level and talk about our response handling in terms of the algorithm. The first thing we do is to parse the response via the createSuggestions() method into an in-memory representation of the suggestions held in the suggestions property. The suggestions property is an array of objects, each with a text and a value property corresponding to the <text> and <value> elements of each <entry> in the XML response.

The remainder of the ajaxUpdate() method’s algorithm is fairly straightforward and should be easy to follow. If no suggestions were found, the pop-up is hidden and the internal value held by the component via a hidden field is cleared. If suggestions were found, the drop-down UI element is created, populated with the suggestions, and displayed, and the selection is updated to be the first one in the list. At this point, the response is considered to be handled, so the this.handlingRequest property discussed earlier is set back to false. Finally, the ajaxUpdate() method checks if there are any pending requests. If so, it sets the pendingRequest flag back to false, takes the current value in the input field for the lastRequestString, and initiates another request cycle via sendRequestForSuggestions().

This concludes the full request/response cycle for the Ajax support and wraps up day 3. We’ve accomplished quite a bit today, plugging in an open source framework that fully “Ajax-enables” our component-meeting requirement, number 7, as well as making sure that it’s done in a way that’s configurable and supports multiple instances on the same page, satisfying requirements 2 and 3. We’ll get into the details of what it means to create, position, show, and hide the UI for the pop-up on day 5. In the meantime, we’ll hook up the component events and take care of the keyboard and mouse handling.

10.5.4Day 4: handling events

Now that the suggest component is fully Ajax enabled, it’s time to hook it into the events produced by the native input field’s responses to the keyboard. If you are an astute reader, you will have guessed that the code that initiates this process was back in the constructor hiding in a call to the injectSuggestBehavior() method. This is the code that initiates all modifications to the DOM of the existing markup, including the event handling, extra inputs, and the container for the suggestions. It’s all done programmatically so we don’t have to touch any of the HTML code on the page, per requirement number 1. The behavior injection is shown in listing 10.25.

 

 

 

 

 

 

 

Refactoring

 

407

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Listing 10.25 The behavior injection

 

 

 

 

 

 

 

 

injectSuggestBehavior: function() {

 

 

 

 

 

 

 

 

if ( this.isIE ) {

 

 

 

 

Remove IE interference

 

 

this.textInput.autocomplete = "off";

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

var keyEventHandler =

 

 

 

Create controller

 

 

new TextSuggestKeyHandler(this);

 

 

 

 

 

 

 

 

 

 

new Insertion.After( this.textInput,

 

 

 

 

 

'<input type="text" id="' + this.id +

 

 

'_preventtsubmit'+

 

 

 

 

 

'" style="display:none"/>' );

 

 

new Insertion.After( this.textInput,

 

 

 

 

 

'<input type="hidden" name="'+

 

 

this.id+'_hidden'+

 

 

 

 

 

'" id="'+this.id+'_hidden'+'"/>' );

 

 

this.createSuggestionsDiv();

 

 

Create UI

 

 

 

 

 

 

 

 

 

 

 

},

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

This method first checks to see if the browser is IE and, if so, sets the proprietary autocomplete property value to off. This keeps the autocompletion pop-up from interfering with our own pop-up. Next an object called TextSuggestKeyHandler is created to be the controller object for brokering the events to the right methods. Yes, the event mechanics are enough of a chore on their own that we split this behavior out into a separate object that we will discuss in a moment. The method next inserts a couple of input elements into the markup. You will recall that in the previous round of our script code, we added a hidden input field for storing the value of the component and an invisible text field to prevent the Enter key from causing the form to be submitted. Because our first requirement forbids us from monkeying with the HTML, we programmatically perform these chores with the two Insertion.After() calls. Insertion.After() is brought to us courtesy of the Prototype library. Finally, createSuggestionsDiv() is called to create the containing div element, which holds the UI for the suggestions.

The TextSuggestKeyHandler

We’ve decided to put the broker of the events into a dedicated controller class. There’s nothing new or revolutionary about it, but it’s definitely a helpful way to separate class responsibilities. In reality, the design could be further separated by creating explicit classes for the model and view roles to provide a full MVC pattern. This exercise is left to the user, but we will break down the

408CHAPTER 10

Type-ahead suggest

architecture of the RSS reader in chapter 13 with a set of classes that satisfies a traditional MVC pattern.

The controller is constructed in the same way as our main class—using Class.create() and an initialize() method. The constructor is shown in listing 10.26.

Listing 10.26 The TextSuggestKeyHandler constructor

TextSuggestKeyHandler = Class.create();

TextSuggestKeyHandler.prototype = {

initialize: function(

textSuggest ) {

 

Reference to TextSuggest

this.textSuggest =

textSuggest;

 

 

this.input

=

this.textSuggest.textInput;

this.addKeyHandling();

},

// rest of API

},

Upon construction, the controller holds a reference to the suggest component along with the native HTML form input field. It then adds the handlers onto the input field via this.addKeyHandling(). The addKeyHandling() method is shown in listing 10.27.

Listing 10.27 The keyboard handler

addKeyHandling: function() { this.input.onkeyup =

this.keyupHandler.bindAsEventListener(this); this.input.onkeydown =

this.keydownHandler.bindAsEventListener(this); this.input.onblur =

this.onblurHandler.bindAsEventListener(this); if ( this.isOpera )

this.input.onkeypress = this.keyupHandler.bindAsEventListener(this);

},

All the relevant events that we need to listen to along with the Opera-specific hack mentioned in the first round of our script development are set up in this method. You will recall that the bindAsEventListener() method is a closure mechanism provided courtesy of the Prototype library. This mechanism allows

Refactoring 409

our handlers to call first-class methods on the controller and normalizes the IE and W3C event models. Very nice, indeed. keyupHandler(), keydownHandler(), onblurHandler(), and their helper methods are mostly a repackaging of what’s already been covered with a few changes. We’ll show the full range of methods next and point out differences from the original script along the way. We’ll start by discussing keydownHandler() and its manipulation of the selection. The keydownHandler() method is shown in listing 10.28.

Listing 10.28 keydownHandler() method

keydownHandler: function(e) { var upArrow = 38;

var downArrow = 40;

if ( e.keyCode == upArrow ) { this.textSuggest.moveSelectionUp();

setTimeout( this.moveCaretToEnd.bind(this), 1 );

}

else if ( e.keyCode == downArrow ) { this.textSuggest.moveSelectionDown();

}

},

The most significant difference from the original script in terms of functionality is in the handling of the arrow keys. The arrow keys in our TextSuggest component handle the movement of the selection based on the onkeydown event rather than the onkeyup event. This is done solely as a usability improvement. It’s somewhat disconcerting to see the selection remain where it is when you press one of the arrow keys, only to see it move once you release the key. keydownHandler() therefore handles the movement of the selection. Note that the selection manipulation methods are methods of the TextSuggest component. The controller, because it saved a reference to the component at construction time, can call these methods through the saved object reference this.textSuggest. The selection manipulation methods of TextSuggest are shown in listing 10.29 for the sake of completeness.

Listing 10.29 TextSuggest selection manipulation methods

moveSelectionUp: function() {

if ( this.selectedIndex > 0 ) { this.updateSelection(this.selectedIndex - 1);

}

},

Clear previous selection

410CHAPTER 10

Type-ahead suggest

moveSelectionDown: function() {

if ( this.selectedIndex < (this.suggestions.length - 1) ) { this.updateSelection(this.selectedIndex + 1);

}

},

updateSelection: function(n) {

var span = $(this.id +"_"+this.selectedIndex); if (span){

span.style.backgroundColor = "";

}

this.selectedIndex = n;

var span = $(this.id+"_"+this.selectedIndex); if (span){

span.style.backgroundColor = this.options.selectionColor;

}

},

The updateSelection() method does all the real work of actually changing the visual state of the selection. It updates the span created in the selection list—we’ll write that code on day 5—and sets its style.backgroundColor to the value specified as the options.selectionColor of our component’s Configuration object.

Before we leave the topic of key-down handling, there’s one more bit of bookkeeping to take care of. Because we handle the arrow keys on the key-down rather than the key-up, we have to change the Up Arrow from its default behavior of moving the caret backward within the text field. We do this with the moveCaretToEnd() method called on a one-millisecond delay via setTimeout. The moveCaretToEnd() method is implemented as shown in listing 10.30.

Listing 10.30 TextSuggest moveCaretToEnd() method

moveCaretToEnd: function() {

var pos = this.input.value.length; if (this.input.setSelectionRange) {

this.input.setSelectionRange(pos,pos);

}

else if(this.input.createTextRange){

var m = this.input.createTextRange(); m.moveStart('character',pos); m.collapse();

m.select();

}

},

Refactoring 411

Now, let’s move onto the key-up handling. The key-up implementation is a bit simpler than the key-down. All it has to do is broker its event to one of a couple of places based on the value in the input field and the key that was pressed. Let’s take a look at the details in listing 10.31.

Listing 10.31 TextSuggest key-up handlers

keyupHandler: function(e) {

if ( this.input.length == 0 && !this.isOpera ) this.textSuggest.hideSuggestions();

if ( !this.handledSpecialKeys(e) ) this.textSuggest.handleTextInput();

},

handledSpecialKeys: function(e) { var enterKey = 13;

var upArrow = 38; var downArrow = 40;

if ( e.keyCode == upArrow || e.keyCode == downArrow ) { return true;

}

else if ( e.keyCode == enterKey ) { this.textSuggest.setInputFromSelection(); return true;

}

return false;

},

The key-up handler first checks to see if the input field contains any text. If not, it tells the TextSuggest component to hide its pop-up list of suggestions. Next it checks to see if the key pressed was one of the special keys: Up Arrow, Down Arrow, or the Enter key. If either the Up or Down Arrow key was pressed, the method just returns without performing any action, since the arrow keys have already been handled during the key-down processing. However, if the Enter key was pressed, the method tells the TextSuggest component to set its input value based on the currently selected item in the suggestion list. Finally, if the input field has a value and the key pressed was not one of the special keys, the key-up handler tells the TextSuggest component to consider that there is some input to be processed via the textSuggest.handleTextInput() method. This is the method of the TextSuggest component that finally calls the Ajax infrastructure we diligently put in place yesterday. The code for handleTextInput() is implemented in listing 10.32.

412CHAPTER 10

Type-ahead suggest

Listing 10.32 Text input handler

handleTextInput: function() {

 

 

var previousRequest =

 

Previous request value

this.lastRequestString;

 

 

this.lastRequestString =

Current request value

this.textInput.value;

 

 

 

if ( this.lastRequestString == "" ) this.hideSuggestions();

else if ( this.lastRequestString != previousRequest ) {

this.sendRequestForSuggestions();

 

Ajax request for data

 

}

 

 

 

},

 

 

 

 

 

 

 

 

 

 

 

The handleTextInput() method first sets a local variable called previousRequest to the prior value of this.lastRequestString. It then sets the lastRequestString property to the current value of the input field so that it can compare the two to make sure that it’s not trying to send a request for the same information that has already been requested. If the request is an empty string, the pop-up list is hidden. If the request is a valid request for new information, the handleTextInput() method calls the sendRequestForSuggestions() method that we wrote yesterday to call the Ajax-based data source to get some suggestions from the server. If the request is the same as the last one, the request is ignored and no action is taken. Finally, the pieces are starting to come together. The construction, the configuration, the Ajax handling, the event handling—it’s almost as if we know what we’re doing. And just in the nick of time; it’s already day 4!

We have one more method of our controller class to cover—the onblur handler. The onblur handler is a very simple method that sets the value of the text field from the current selection and hides the suggestion. The implementation is as follows:

onblurHandler: function(e) {

if ( this.textSuggest.suggestionsDiv.style.display == '' ) this.textSuggest.setInputFromSelection();

this.textSuggest.hideSuggestions();

}

The onblurHandler and handledSpecialKeys both reference a method of the TextSuggest component that we’ve not seen yet—setInputFromSelection(). This method does essentially the same thing that our SetText() function did earlier— namely, to take the currently selected suggestion; set both the input field and the hidden field with its text and value, respectively; and hide the list of suggestions. The implementation is shown here:

setInputFromSelection: function() {

 

Refactoring

413

 

 

 

 

 

 

var hiddenInput

=

$(

this.id + "_hidden" );

 

 

 

var suggestion

=

this.suggestions[ this.selectedIndex ];

 

this.textInput.value

= suggestion.text;

 

 

 

Update visible value

 

 

 

 

hiddenInput.value

 

= suggestion.value;

 

 

Update hidden value

 

 

 

 

 

this.hideSuggestions();

 

 

 

}

 

 

 

 

 

 

 

 

We may have put in a little overtime to accomplish all that’s been done today. We created a controller class to handle all of our event management. We used the Prototype library’s bindAsEventListener() method to automatically create closures for us and normalize the IE and W3C event models. We implemented our key-up/down handlers to encapsulate the complexities of processing the selection as well as normal text input. We ensured that we initiate only requests for new information. We managed the showing and hiding of the suggestions UI as appropriate. We updated the DOM programmatically to manage the hidden input value and the invisible text field that prevents form submission when the Enter key is pressed. And we handled the updating of the hidden and visible values of the TextSuggest component. On day 5, we wrap a bow around our refactored component by implementing all the methods required to create the pop-up, position it, show it, hide it, and manage its mouse events. The once dim light at the end of the tunnel is now clearly in view.

10.5.5Day 5: the suggestions pop-up UI

Now that we’re fully plugged in, so to speak, it’s time to tie up all the loose ends. To this point, we’ve created infrastructure for configurability and defaults, Ajax request and response handling, and the events that tie everything together. All that’s left to cover is the graphical part. What we’re referring to here, obviously, is the pop-up list of suggestions and all that implies. The tasks left to handle with respect to the UI are as follows:

Creation of the suggestion pop-up UI. This entails the creation of the div for the suggestions as well as the span for each suggestion.

The positioning of the pop-up.

The population of the pop-up with suggestions.

The showing and hiding of the suggestions.

414

CHAPTER 10

 

Type-ahead suggest

Creating the suggestion pop-up

Let’s go back and examine the implementation of the injectSuggestBehavior() method. Recall that this code was more or less the entry point to all the DOM manipulation done by the TextSuggest component:

injectSuggestBehavior: function() { // HTML Dom Behavior Injection...

this.createSuggestionsDiv();

},

The last line of the injectSuggestBehavior() method calls the createSuggestionsDiv() method, which creates the outermost containing div of the suggestion pop-up. Since this is the container of all GUI artifacts, it’s the logical place to start looking at UI code. The details of the implementation are shown in listing 10.33.

Listing 10.33 Creating the suggestion pop-up UI

createSuggestionsDiv: function() {

 

this.suggestionsDiv =

b Create the div

document.createElement("div");

this.suggestionsDiv.className =

c Style the div

this.options.suggestDivClassName;

var divStyle = this.suggestionsDiv.style;

divStyle.position = 'absolute'; divStyle.zIndex = 101; divStyle.display = "none";

d Add behavioral style

this.textInput.parentNode.appendChild

e Insert into document

(this.suggestionsDiv);

},

The creation method of the container has four basic responsibilities. First, it has to create the DIV via the document’s createElement() API b.

Second, it has to style the DIV according to the client configuration c. Recall that one of our requirements was to make the CSS styling of each component instance individually configurable. We achieve that in this case by setting the div’s className attribute according to the suggestDivClassName property of the options object. You will recall that we set the default value of this property to suggestDiv within the setOptions method. So if the user doesn’t explicitly specify a value for a property, this is what she will get. This is a convenient feature because it allows the client of our component to have a default stylesheet that uses our

Refactoring 415

default class names to style all TextSuggest component instances used across the application. Other stylesheets could also be provided (for example, productor customer-specific stylesheets) that override the definitions of these standard style names. And finally, an individual page can override the value of the suggestDivClassName parameter to provide a page-level or instance-level styling to the component. Sounds pretty flexible to us.

There are certain aspects of the style of the pop-up that are nonnegotiable, annotated as “Behavioral style,” so we style them explicitly through the style attribute of the element d. Note that anything styled programmatically via the style attribute overrides anything specified via a CSS className, typically by a stylesheet. These nonnegotiable aspects are 1) position='absolute' because the component must manage the positioning of the div internally, 2) zIndex=101, which we use to make sure the pop-up is on top of everything on the page, and 3) display="none" because the pop-up has to be hidden from the user’s view until the user’s keystrokes trigger it. Note that the value of 101 for the zIndex is somewhat arbitrary.

Finally, the method inserts the div into the document as a sibling of the text field e. The parent in this case really doesn’t matter, since the div will be positioned absolutely.

Positioning the pop-up

Now that our pop-up has been created, at some point it will have to be shown. But before it can be shown, it has to be positioned. When we show the pop-up, we want it to appear just below the text field and to be aligned with the left side of the text field. Let’s write the positionSuggestionsDiv method in listing 10.34.

Listing 10.34 Positioning the pop-up UI

positionSuggestionsDiv: function() {

var textPos = RicoUtil.toDocumentPosition(this.textInput); var divStyle = this.suggestionsDiv.style;

divStyle.top = (textPos.y + this.textInput.offsetHeight) + "px";

divStyle.left = textPos.x + "px";

if ( this.options.matchTextWidth ) divStyle.width = (this.textInput.offsetWidth –

this.padding()) + "px";

},

416CHAPTER 10

Type-ahead suggest

You will recall that in the previous version of this script, we wrote a method to calculate the absolute position of the text field. In this refactored version, we are relying on a utility method provided by Rico—toDocumentPosition(). All we have to do is to use this method to get our reference point and perform the appropriate calculations to get our pop-up below and align on the left with the text field. We then check for the existence of the configuration option matchTextWidth, and if it is true, we also size the width of the div element to match the width of the text input. Note that we adjust the width by the padding value. We do this because, as you recall, we’ve allowed the div element to be externally styled through a CSS class. We don’t know if the user will have put margins and borders on the component, which would throw off the visual alignment to the width of the text field. Let’s write a padding() method (listing 10.35) to compute the left and right padding values and margins to subtract from the overall width.

Listing 10.35 Calculation of left and right padding

padding: function() { try{

var styleFunc = RicoUtil.getElementsComputedStyle;

var lPad

= styleFunc(

this.suggestionsDiv,

 

 

"paddingLeft",

 

 

"padding-left" );

var rPad

= styleFunc(

this.suggestionsDiv,

 

 

"paddingRight",

 

 

"padding-right" );

var lBorder = styleFunc(

this.suggestionsDiv,

 

 

"borderLeftWidth",

 

 

"border-left-width" );

var rBorder = styleFunc(

this.suggestionsDiv,

 

 

"borderRightWidth",

 

 

"border-right-width" );

lPad

= isNaN(lPad)

? 0

: lPad;

rPad

= isNaN(rPad)

? 0

: rPad;

lBorder = isNaN(lBorder)

? 0

: lBorder;

rBorder = isNaN(rBorder)

? 0

: rBorder;

return parseInt(lPad) + parseInt(rPad) + parseInt(lBorder) + parseInt(rBorder);

}catch (e){ return 0; } },

Getting the calculated style of an element—the actual value of an attribute regardless of how it was set—is tricky business. To achieve this, IE provides a proprietary currentStyle attribute for each element. Mozilla-based browsers use a getComputedStyle() method of the defaultView property of the document to

Refactoring 417

calculate this. Each one of these mechanisms expects a different specification for the attribute being queried, as well. The IE currentStyle expects style attributes specified via the JavaScript-like binding (for example, borderRightWidth), whereas the Mozilla getComputedStyle() expects attributes specified with the stylesheetlike syntax (for example, border-right-width). Luckily, Rico provides a method that takes care of all of this for us—RicoUtil.getElementsComputedStyle(). We just pass it the element, the IE name for the attribute, and the Mozilla name for the attribute, and the method returns a value. Our method here gets the values of the left and right borders and margins, sums them up, and returns them.

The Rico.getElementsComputedStyle() is known to have issues with some versions of Safari, and so we provide a default return value within a try...catch block.

Creating the pop-up contents

Now that we have the code to create and position the pop-up, we need to write a method to populate it with actual suggestions before it can be useful. Recall that our ajaxUpdate() method parses the XML from the response into an array of suggestion objects. And, if at least one suggestion exists, it calls a method named this.updateSugggestionsDiv(). This method is the transformer of the in-memory collection of suggestions to actual SPAN elements within the pop-up div. Let’s look at how that’s done now:

updateSuggestionsDiv: function() {

 

Remove prior content

this.suggestionsDiv.innerHTML = "";

 

 

var suggestLines = this.createSuggestionSpans();

 

 

for (var i = 0; i < suggestLines.length; i++)

 

Create new

 

this.suggestionsDiv.appendChild(suggestLines[i]);

 

content

},

 

 

 

 

This method is deceptively simple, but there’s still lots of work to do, so hang with us. This method simply sets the value of the innerHTML property of the suggestionsDiv created earlier to an empty string in order to wipe out any prior content. Then it calls createSuggestionSpans() to create a span for each suggestion in the suggestions array. Finally, it iterates over the created spans and appends them to the div. This is where the real work starts. Let’s continue by looking at createSuggestionSpans() in listing 10.36 to see what’s involved in creating them.

Listing 10.36 Creation of suggestion list items

createSuggestionSpans: function() { var regExpFlags = "";

if ( this.options.ignoreCase ) regExpFlags = 'i';

var startRegExp = "^";

418CHAPTER 10

Type-ahead suggest

if ( this.options.matchAnywhere ) startRegExp = '';

var regExp = new RegExp( startRegExp + this.lastRequestString,

regExpFlags );

var suggestionSpans = [];

for ( var i = 0 ; i < this.suggestions.length ; i++ ) suggestionSpans.push(

this.createSuggestionSpan( i, regExp ) );

return suggestionSpans;

},

This method first looks at our options object to find the value of the ignoreCase and matchAnywhere properties. This has to be done so that a regular expression can be created with the appropriate parameters that will facilitate the retrieval of the portion of the string in the response that actually matches what the user has typed in. The method then iterates over the suggestions property, which you will recall is an array of objects that have a .text and a .value property. For each suggestion in the array, the createSuggestionSpan() method is called with the index of the suggestion and the regular expression created earlier. All the real work is done in createSuggestionSpan(), shown in listing 10.37.

Listing 10.37 Creation of a list item span

createSuggestionSpan: function( n, regExp ) { var suggestion = this.suggestions[n];

var suggestionSpan = document.createElement("span"); suggestionSpan.className = this.options.suggestionClassName; suggestionSpan.style.width = '100%'; suggestionSpan.style.display = 'block';

suggestionSpan.id = this.id + "_" + n; suggestionSpan.onmouseover =

this.mouseoverHandler.bindAsEventListener(this); suggestionSpan.onclick =

this.itemClickHandler.bindAsEventListener(this);

var textValues = this.splitTextValues( suggestion.text, this.lastRequestString.length, regExp );

var textMatchSpan = document.createElement("span");

 

textMatchSpan.id

=

this.id + "_match_" +

n;

textMatchSpan.className

=

this.options.matchClassName;

Refactoring 419

textMatchSpan.onmouseover = this.mouseoverHandler.bindAsEventListener(this);

textMatchSpan.onclick = this.itemClickHandler.bindAsEventListener(this);

textMatchSpan.appendChild( document.createTextNode(textValues.mid) );

suggestionSpan.appendChild( document.createTextNode(textValues.start) );

suggestionSpan.appendChild(textMatchSpan);

suggestionSpan.appendChild( document.createTextNode(textValues.end) );

return suggestionSpan;

},

This task is starting to look daunting, but don’t bail out just yet. This method probably looks more complicated than it is, although it does quite a bit of work. Perhaps it would be best to back up at this point and look at this method in terms of what it is attempting to produce: namely, some HTML for a suggestion. Let’s imagine HTML markup for a suggestion that looks something like this:

<span>before <span>matching text</span>, and after</span>

This is a gross simplification of what’s actually generated, but it illustrates the structure. Suppose that the user has typed “matching text,” and one of the values in the database is “before matching text, and after.” What’s generated for a suggestion is basically what we just showed but with some extra attributes added to the spans for identification, styling, and event handling. All the hard work of splitting up the before and after portions of the text is done by the following line of code:

var textValues = this.splitTextValues( suggestion.text, this.lastRequestString.length, regExp );

The textValues value returned is an object that has three properties: .start,

.mid, and .end. So in the example just shown, textValues is an object that looks like the following:

textValues = { start: 'before ',

mid:

'matching text',

 

end:

', and after'

};

Finally, the splitTextValues() method implementation is shown here:

420

CHAPTER 10

 

 

 

Type-ahead suggest

 

 

 

splitTextValues: function( text, len, regExp ) {

 

var startPos

=

text.search(regExp);

 

var matchText =

text.substring( startPos, startPos + len );

 

var startText =

startPos == 0 ?

 

 

 

"" : text.substring(0, startPos);

 

var endText

=

text.substring( startPos + len );

 

return { start: startText, mid: matchText, end: endText };

 

},

 

 

Now that we’ve covered the basic structure of a suggestion span, let’s talk about the relevant attributes that get generated on the spans. Both the outer span and the inner span are created with CSS class names based on the value of the suggestionClassName and matchClassName properties of the Options object, respectively. Just as the suggestionsDiv has an entirely customizable look and feel via CSS classes, so does all of the internal HTML structure of each suggestion.

The other noteworthy attributes generated within the spans are ID attributes so that the spans can be retrieved later by the aforementioned event handlers. An onmouseover event handler has to be placed on the spans so that the component can update the selection to the suggestion that the mouse is currently over. Also, an onclick event handler must be placed on each suggestion so that when a suggestion line is clicked on, its value can be placed within the text field. The two event handlers are implemented as shown in listing 10.38.

Listing 10.38 List item mouse event handlers

mouseoverHandler: function(e) {

var src = e.srcElement ? e.srcElement : e.target;

var index = parseInt(src.id.substring(src.id.lastIndexOf('_')+1)); this.updateSelection(index);

},

itemClickHandler: function(e) { this.mouseoverHandler(e); this.hideSuggestions(); this.textInput.focus();

},

mouseoverHandler() simply finds the target of the event and parses out the ID that we generated on it to get an index representing which suggestion it is. It can then use the updateSelection() method we wrote on day 4 to update the selection to the suggestion over which the mouse is currently hovering.

Similarly, itemClickHandler() has to update the selection, so it just calls mouseoverHandler() to do the selection update work. It then has to do the additional