]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/autocomplete/autocomplete.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / autocomplete / autocomplete.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 /////////////////////////////////////////////////////////////////////////////
8 //
9 // YAHOO.widget.DataSource Backwards Compatibility
10 //
11 /////////////////////////////////////////////////////////////////////////////
12
13 YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
14
15 YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
16
17 YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
18     var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
19     DS._aDeprecatedSchema = aSchema;
20     return DS;
21 };
22
23 YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
24     var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
25     DS._aDeprecatedSchema = aSchema;
26     return DS;
27 };
28
29 YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
30 YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
31 YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
32
33 // TODO: widget.DS_ScriptNode.scriptCallbackParam
34
35
36
37  /**
38  * The AutoComplete control provides the front-end logic for text-entry suggestion and
39  * completion functionality.
40  *
41  * @module autocomplete
42  * @requires yahoo, dom, event, datasource
43  * @optional animation
44  * @namespace YAHOO.widget
45  * @title AutoComplete Widget
46  */
47
48 /****************************************************************************/
49 /****************************************************************************/
50 /****************************************************************************/
51
52 /**
53  * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
54  * auto completion widget.  Some key features:
55  * <ul>
56  * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
57  * <li>The drop down container can "roll down" or "fly out" via configurable
58  * animation</li>
59  * <li>UI look-and-feel customizable through CSS, including container
60  * attributes, borders, position, fonts, etc</li>
61  * </ul>
62  *
63  * @class AutoComplete
64  * @constructor
65  * @param elInput {HTMLElement} DOM element reference of an input field.
66  * @param elInput {String} String ID of an input field.
67  * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
68  * @param elContainer {String} String ID of an existing DIV.
69  * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
70  * @param oConfigs {Object} (optional) Object literal of configuration params.
71  */
72 YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
73     if(elInput && elContainer && oDataSource) {
74         // Validate DataSource
75         if(oDataSource && YAHOO.lang.isFunction(oDataSource.sendRequest)) {
76             this.dataSource = oDataSource;
77         }
78         else {
79             return;
80         }
81
82         // YAHOO.widget.DataSource schema backwards compatibility
83         // Converted deprecated schema into supported schema
84         // First assume key data is held in position 0 of results array
85         this.key = 0;
86         var schema = oDataSource.responseSchema;
87         // An old school schema has been defined in the deprecated DataSource constructor
88         if(oDataSource._aDeprecatedSchema) {
89             var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
90             if(YAHOO.lang.isArray(aDeprecatedSchema)) {
91                 
92                 if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) || 
93                 (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
94                     // Store the resultsList
95                     schema.resultsList = aDeprecatedSchema[0];
96                     // Store the key
97                     this.key = aDeprecatedSchema[1];
98                     // Only resultsList and key are defined, so grab all the data
99                     schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
100                 }
101                 else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
102                     schema.resultNode = aDeprecatedSchema[0];
103                     this.key = aDeprecatedSchema[1];
104                     schema.fields = aDeprecatedSchema.slice(1);
105                 }                
106                 else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
107                     schema.recordDelim = aDeprecatedSchema[0];
108                     schema.fieldDelim = aDeprecatedSchema[1];
109                 }                
110                 oDataSource.responseSchema = schema;
111             }
112         }
113         
114         // Validate input element
115         if(YAHOO.util.Dom.inDocument(elInput)) {
116             if(YAHOO.lang.isString(elInput)) {
117                     this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
118                     this._elTextbox = document.getElementById(elInput);
119             }
120             else {
121                 this._sName = (elInput.id) ?
122                     "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
123                     "instance" + YAHOO.widget.AutoComplete._nIndex;
124                 this._elTextbox = elInput;
125             }
126             YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
127         }
128         else {
129             return;
130         }
131
132         // Validate container element
133         if(YAHOO.util.Dom.inDocument(elContainer)) {
134             if(YAHOO.lang.isString(elContainer)) {
135                     this._elContainer = document.getElementById(elContainer);
136             }
137             else {
138                 this._elContainer = elContainer;
139             }
140             if(this._elContainer.style.display == "none") {
141             }
142             
143             // For skinning
144             var elParent = this._elContainer.parentNode;
145             var elTag = elParent.tagName.toLowerCase();
146             if(elTag == "div") {
147                 YAHOO.util.Dom.addClass(elParent, "yui-ac");
148             }
149             else {
150             }
151         }
152         else {
153             return;
154         }
155
156         // Default applyLocalFilter setting is to enable for local sources
157         if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
158             this.applyLocalFilter = true;
159         }
160         
161         // Set any config params passed in to override defaults
162         if(oConfigs && (oConfigs.constructor == Object)) {
163             for(var sConfig in oConfigs) {
164                 if(sConfig) {
165                     this[sConfig] = oConfigs[sConfig];
166                 }
167             }
168         }
169
170         // Initialization sequence
171         this._initContainerEl();
172         this._initProps();
173         this._initListEl();
174         this._initContainerHelperEls();
175
176         // Set up events
177         var oSelf = this;
178         var elTextbox = this._elTextbox;
179
180         // Dom events
181         YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
182         YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
183         YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
184         YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
185         YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
186         YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
187         YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
188         YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
189         YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
190         YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
191         YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
192
193         // Custom events
194         this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
195         this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
196         this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
197         this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
198         this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
199         this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
200         this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
201         this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
202         this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
203         this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
204         this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
205         this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
206         this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
207         this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
208         this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
209         this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
210         this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
211         this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
212         
213         // Finish up
214         elTextbox.setAttribute("autocomplete","off");
215         YAHOO.widget.AutoComplete._nIndex++;
216     }
217     // Required arguments were not found
218     else {
219     }
220 };
221
222 /////////////////////////////////////////////////////////////////////////////
223 //
224 // Public member variables
225 //
226 /////////////////////////////////////////////////////////////////////////////
227
228 /**
229  * The DataSource object that encapsulates the data used for auto completion.
230  * This object should be an inherited object from YAHOO.widget.DataSource.
231  *
232  * @property dataSource
233  * @type YAHOO.widget.DataSource
234  */
235 YAHOO.widget.AutoComplete.prototype.dataSource = null;
236
237 /**
238  * By default, results from local DataSources will pass through the filterResults
239  * method to apply a client-side matching algorithm. 
240  * 
241  * @property applyLocalFilter
242  * @type Boolean
243  * @default true for local arrays and json, otherwise false
244  */
245 YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
246
247 /**
248  * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
249  * enabled. 
250  * 
251  * @property queryMatchCase
252  * @type Boolean
253  * @default false
254  */
255 YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
256
257 /**
258  * When applyLocalFilter is true, results can  be locally filtered to return
259  * matching strings that "contain" the query string rather than simply "start with"
260  * the query string.
261  * 
262  * @property queryMatchContains
263  * @type Boolean
264  * @default false
265  */
266 YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
267
268 /**
269  * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
270  * true, substrings of queries will return matching cached results. For
271  * instance, if the first query is for "abc" susequent queries that start with
272  * "abc", like "abcd", will be queried against the cache, and not the live data
273  * source. Recommended only for DataSources that return comprehensive results
274  * for queries with very few characters.
275  *
276  * @property queryMatchSubset
277  * @type Boolean
278  * @default false
279  *
280  */
281 YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
282
283 /**
284  * Number of characters that must be entered before querying for results. A negative value
285  * effectively turns off the widget. A value of 0 allows queries of null or empty string
286  * values.
287  *
288  * @property minQueryLength
289  * @type Number
290  * @default 1
291  */
292 YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
293
294 /**
295  * Maximum number of results to display in results container.
296  *
297  * @property maxResultsDisplayed
298  * @type Number
299  * @default 10
300  */
301 YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
302
303 /**
304  * Number of seconds to delay before submitting a query request.  If a query
305  * request is received before a previous one has completed its delay, the
306  * previous request is cancelled and the new request is set to the delay. If 
307  * typeAhead is also enabled, this value must always be less than the typeAheadDelay
308  * in order to avoid certain race conditions. 
309  *
310  * @property queryDelay
311  * @type Number
312  * @default 0.2
313  */
314 YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
315
316 /**
317  * If typeAhead is true, number of seconds to delay before updating input with
318  * typeAhead value. In order to prevent certain race conditions, this value must
319  * always be greater than the queryDelay.
320  *
321  * @property typeAheadDelay
322  * @type Number
323  * @default 0.5
324  */
325 YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
326
327 /**
328  * When IME usage is detected or interval detection is explicitly enabled,
329  * AutoComplete will detect the input value at the given interval and send a
330  * query if the value has changed.
331  *
332  * @property queryInterval
333  * @type Number
334  * @default 500
335  */
336 YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
337
338 /**
339  * Class name of a highlighted item within results container.
340  *
341  * @property highlightClassName
342  * @type String
343  * @default "yui-ac-highlight"
344  */
345 YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
346
347 /**
348  * Class name of a pre-highlighted item within results container.
349  *
350  * @property prehighlightClassName
351  * @type String
352  */
353 YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
354
355 /**
356  * Query delimiter. A single character separator for multiple delimited
357  * selections. Multiple delimiter characteres may be defined as an array of
358  * strings. A null value or empty string indicates that query results cannot
359  * be delimited. This feature is not recommended if you need forceSelection to
360  * be true.
361  *
362  * @property delimChar
363  * @type String | String[]
364  */
365 YAHOO.widget.AutoComplete.prototype.delimChar = null;
366
367 /**
368  * Whether or not the first item in results container should be automatically highlighted
369  * on expand.
370  *
371  * @property autoHighlight
372  * @type Boolean
373  * @default true
374  */
375 YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
376
377 /**
378  * If autohighlight is enabled, whether or not the input field should be automatically updated
379  * with the first query result as the user types, auto-selecting the substring portion
380  * of the first result that the user has not yet typed.
381  *
382  * @property typeAhead
383  * @type Boolean
384  * @default false
385  */
386 YAHOO.widget.AutoComplete.prototype.typeAhead = false;
387
388 /**
389  * Whether or not to animate the expansion/collapse of the results container in the
390  * horizontal direction.
391  *
392  * @property animHoriz
393  * @type Boolean
394  * @default false
395  */
396 YAHOO.widget.AutoComplete.prototype.animHoriz = false;
397
398 /**
399  * Whether or not to animate the expansion/collapse of the results container in the
400  * vertical direction.
401  *
402  * @property animVert
403  * @type Boolean
404  * @default true
405  */
406 YAHOO.widget.AutoComplete.prototype.animVert = true;
407
408 /**
409  * Speed of container expand/collapse animation, in seconds..
410  *
411  * @property animSpeed
412  * @type Number
413  * @default 0.3
414  */
415 YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
416
417 /**
418  * Whether or not to force the user's selection to match one of the query
419  * results. Enabling this feature essentially transforms the input field into a
420  * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
421  * defined.
422  *
423  * @property forceSelection
424  * @type Boolean
425  * @default false
426  */
427 YAHOO.widget.AutoComplete.prototype.forceSelection = false;
428
429 /**
430  * Whether or not to allow browsers to cache user-typed input in the input
431  * field. Disabling this feature will prevent the widget from setting the
432  * autocomplete="off" on the input field. When autocomplete="off"
433  * and users click the back button after form submission, user-typed input can
434  * be prefilled by the browser from its cache. This caching of user input may
435  * not be desired for sensitive data, such as credit card numbers, in which
436  * case, implementers should consider setting allowBrowserAutocomplete to false.
437  *
438  * @property allowBrowserAutocomplete
439  * @type Boolean
440  * @default true
441  */
442 YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
443
444 /**
445  * Enabling this feature prevents the toggling of the container to a collapsed state.
446  * Setting to true does not automatically trigger the opening of the container.
447  * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.   
448  *
449  * @property alwaysShowContainer
450  * @type Boolean
451  * @default false
452  */
453 YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
454
455 /**
456  * Whether or not to use an iFrame to layer over Windows form elements in
457  * IE. Set to true only when the results container will be on top of a
458  * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
459  * 5.5 < IE < 7).
460  *
461  * @property useIFrame
462  * @type Boolean
463  * @default false
464  */
465 YAHOO.widget.AutoComplete.prototype.useIFrame = false;
466
467 /**
468  * Whether or not the results container should have a shadow.
469  *
470  * @property useShadow
471  * @type Boolean
472  * @default false
473  */
474 YAHOO.widget.AutoComplete.prototype.useShadow = false;
475
476 /**
477  * Whether or not the input field should be updated with selections.
478  *
479  * @property suppressInputUpdate
480  * @type Boolean
481  * @default false
482  */
483 YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
484
485 /**
486  * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
487  * resultsTypeList to true will take each object literal result returned by
488  * DataSource and flatten into an array.  
489  *
490  * @property resultTypeList
491  * @type Boolean
492  * @default true
493  */
494 YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
495
496 /**
497  * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and 
498  * the "query" param/value pair. To prevent this behavior, implementers should
499  * set this value to false. To more fully customize the query syntax, implementers
500  * should override the generateRequest() method. 
501  *
502  * @property queryQuestionMark
503  * @type Boolean
504  * @default true
505  */
506 YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
507
508 /**
509  * If true, before each time the container expands, the container element will be
510  * positioned to snap to the bottom-left corner of the input element. If
511  * autoSnapContainer is set to false, this positioning will not be done.  
512  *
513  * @property autoSnapContainer
514  * @type Boolean
515  * @default true
516  */
517 YAHOO.widget.AutoComplete.prototype.autoSnapContainer = true;
518
519 /////////////////////////////////////////////////////////////////////////////
520 //
521 // Public methods
522 //
523 /////////////////////////////////////////////////////////////////////////////
524
525  /**
526  * Public accessor to the unique name of the AutoComplete instance.
527  *
528  * @method toString
529  * @return {String} Unique name of the AutoComplete instance.
530  */
531 YAHOO.widget.AutoComplete.prototype.toString = function() {
532     return "AutoComplete " + this._sName;
533 };
534
535  /**
536  * Returns DOM reference to input element.
537  *
538  * @method getInputEl
539  * @return {HTMLELement} DOM reference to input element.
540  */
541 YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
542     return this._elTextbox;
543 };
544
545  /**
546  * Returns DOM reference to container element.
547  *
548  * @method getContainerEl
549  * @return {HTMLELement} DOM reference to container element.
550  */
551 YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
552     return this._elContainer;
553 };
554
555  /**
556  * Returns true if widget instance is currently active.
557  *
558  * @method isFocused
559  * @return {Boolean} Returns true if widget instance is currently active.
560  */
561 YAHOO.widget.AutoComplete.prototype.isFocused = function() {
562     return this._bFocused;
563 };
564
565  /**
566  * Returns true if container is in an expanded state, false otherwise.
567  *
568  * @method isContainerOpen
569  * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
570  */
571 YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
572     return this._bContainerOpen;
573 };
574
575 /**
576  * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
577  *
578  * @method getListEl
579  * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
580  */
581 YAHOO.widget.AutoComplete.prototype.getListEl = function() {
582     return this._elList;
583 };
584
585 /**
586  * Public accessor to the matching string associated with a given &lt;li&gt; result.
587  *
588  * @method getListItemMatch
589  * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
590  * @return {String} Matching string.
591  */
592 YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
593     if(elListItem._sResultMatch) {
594         return elListItem._sResultMatch;
595     }
596     else {
597         return null;
598     }
599 };
600
601 /**
602  * Public accessor to the result data associated with a given &lt;li&gt; result.
603  *
604  * @method getListItemData
605  * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
606  * @return {Object} Result data.
607  */
608 YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
609     if(elListItem._oResultData) {
610         return elListItem._oResultData;
611     }
612     else {
613         return null;
614     }
615 };
616
617 /**
618  * Public accessor to the index of the associated with a given &lt;li&gt; result.
619  *
620  * @method getListItemIndex
621  * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
622  * @return {Number} Index.
623  */
624 YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
625     if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
626         return elListItem._nItemIndex;
627     }
628     else {
629         return null;
630     }
631 };
632
633 /**
634  * Sets HTML markup for the results container header. This markup will be
635  * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
636  *
637  * @method setHeader
638  * @param sHeader {String} HTML markup for results container header.
639  */
640 YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
641     if(this._elHeader) {
642         var elHeader = this._elHeader;
643         if(sHeader) {
644             elHeader.innerHTML = sHeader;
645             elHeader.style.display = "";
646         }
647         else {
648             elHeader.innerHTML = "";
649             elHeader.style.display = "none";
650         }
651     }
652 };
653
654 /**
655  * Sets HTML markup for the results container footer. This markup will be
656  * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
657  *
658  * @method setFooter
659  * @param sFooter {String} HTML markup for results container footer.
660  */
661 YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
662     if(this._elFooter) {
663         var elFooter = this._elFooter;
664         if(sFooter) {
665                 elFooter.innerHTML = sFooter;
666                 elFooter.style.display = "";
667         }
668         else {
669             elFooter.innerHTML = "";
670             elFooter.style.display = "none";
671         }
672     }
673 };
674
675 /**
676  * Sets HTML markup for the results container body. This markup will be
677  * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
678  *
679  * @method setBody
680  * @param sBody {String} HTML markup for results container body.
681  */
682 YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
683     if(this._elBody) {
684         var elBody = this._elBody;
685         YAHOO.util.Event.purgeElement(elBody, true);
686         if(sBody) {
687             elBody.innerHTML = sBody;
688             elBody.style.display = "";
689         }
690         else {
691             elBody.innerHTML = "";
692             elBody.style.display = "none";
693         }
694         this._elList = null;
695     }
696 };
697
698 /**
699 * A function that converts an AutoComplete query into a request value which is then
700 * passed to the DataSource's sendRequest method in order to retrieve data for 
701 * the query. By default, returns a String with the syntax: "query={query}"
702 * Implementers can customize this method for custom request syntaxes.
703
704 * @method generateRequest
705 * @param sQuery {String} Query string
706 * @return {MIXED} Request
707 */
708 YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
709     var dataType = this.dataSource.dataType;
710     
711     // Transform query string in to a request for remote data
712     // By default, local data doesn't need a transformation, just passes along the query as is.
713     if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
714         // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
715         if(!this.dataSource.connMethodPost) {
716             sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
717                 (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");        
718         }
719         // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
720         else {
721             sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
722                 (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
723         }
724     }
725     // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
726     else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
727         sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
728             (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");    
729     }
730     
731     return sQuery;
732 };
733
734 /**
735  * Makes query request to the DataSource.
736  *
737  * @method sendQuery
738  * @param sQuery {String} Query string.
739  */
740 YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
741     // Activate focus for a new interaction
742     this._bFocused = true;
743     
744     // Adjust programatically sent queries to look like they were input by user
745     // when delimiters are enabled
746     var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
747     this._sendQuery(newQuery);
748 };
749
750 /**
751  * Snaps container to bottom-left corner of input element
752  *
753  * @method snapContainer
754  */
755 YAHOO.widget.AutoComplete.prototype.snapContainer = function() {
756     var oTextbox = this._elTextbox,
757         pos = YAHOO.util.Dom.getXY(oTextbox);
758     pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
759     YAHOO.util.Dom.setXY(this._elContainer,pos);
760 };
761
762 /**
763  * Expands container.
764  *
765  * @method expandContainer
766  */
767 YAHOO.widget.AutoComplete.prototype.expandContainer = function() {
768     this._toggleContainer(true);
769 };
770
771 /**
772  * Collapses container.
773  *
774  * @method collapseContainer
775  */
776 YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
777     this._toggleContainer(false);
778 };
779
780 /**
781  * Clears entire list of suggestions.
782  *
783  * @method clearList
784  */
785 YAHOO.widget.AutoComplete.prototype.clearList = function() {
786     var allItems = this._elList.childNodes,
787         i=allItems.length-1;
788     for(; i>-1; i--) {
789           allItems[i].style.display = "none";
790     }
791 };
792
793 /**
794  * Handles subset matching for when queryMatchSubset is enabled.
795  *
796  * @method getSubsetMatches
797  * @param sQuery {String} Query string.
798  * @return {Object} oParsedResponse or null. 
799  */
800 YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
801     var subQuery, oCachedResponse, subRequest;
802     // Loop through substrings of each cached element's query property...
803     for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
804         subRequest = this.generateRequest(sQuery.substr(0,i));
805         this.dataRequestEvent.fire(this, subQuery, subRequest);
806         
807         // If a substring of the query is found in the cache
808         oCachedResponse = this.dataSource.getCachedResponse(subRequest);
809         if(oCachedResponse) {
810             return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
811         }
812     }
813     return null;
814 };
815
816 /**
817  * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
818  * handle responseStripAfter cleanup.
819  *
820  * @method preparseRawResponse
821  * @param sQuery {String} Query string.
822  * @return {Object} oParsedResponse or null. 
823  */
824 YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
825     var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
826         oFullResponse.indexOf(this.responseStripAfter) : -1;
827     if(nEnd != -1) {
828         oFullResponse = oFullResponse.substring(0,nEnd);
829     }
830     return oFullResponse;
831 };
832
833 /**
834  * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
835  * filter results through a simple client-side matching algorithm. 
836  *
837  * @method filterResults
838  * @param sQuery {String} Original request.
839  * @param oFullResponse {Object} Full response object.
840  * @param oParsedResponse {Object} Parsed response object.
841  * @param oCallback {Object} Callback object. 
842  * @return {Object} Filtered response object.
843  */
844
845 YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
846     // If AC has passed a query string value back to itself, grab it
847     if(oCallback && oCallback.argument && oCallback.argument.query) {
848         sQuery = oCallback.argument.query;
849     }
850
851     // Only if a query string is available to match against
852     if(sQuery && sQuery !== "") {
853         // First make a copy of the oParseResponse
854         oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
855         
856         var oAC = oCallback.scope,
857             oDS = this,
858             allResults = oParsedResponse.results, // the array of results
859             filteredResults = [], // container for filtered results,
860             nMax = oAC.maxResultsDisplayed, // max to find
861             bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
862             bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
863             
864         // Loop through each result object...
865         for(var i=0, len=allResults.length; i<len; i++) {
866             var oResult = allResults[i];
867
868             // Grab the data to match against from the result object...
869             var sResult = null;
870             
871             // Result object is a simple string already
872             if(YAHOO.lang.isString(oResult)) {
873                 sResult = oResult;
874             }
875             // Result object is an array of strings
876             else if(YAHOO.lang.isArray(oResult)) {
877                 sResult = oResult[0];
878             
879             }
880             // Result object is an object literal of strings
881             else if(this.responseSchema.fields) {
882                 var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
883                 sResult = oResult[key];
884             }
885             // Backwards compatibility
886             else if(this.key) {
887                 sResult = oResult[this.key];
888             }
889             
890             if(YAHOO.lang.isString(sResult)) {
891                 
892                 var sKeyIndex = (bMatchCase) ?
893                 sResult.indexOf(decodeURIComponent(sQuery)) :
894                 sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
895
896                 // A STARTSWITH match is when the query is found at the beginning of the key string...
897                 if((!bMatchContains && (sKeyIndex === 0)) ||
898                 // A CONTAINS match is when the query is found anywhere within the key string...
899                 (bMatchContains && (sKeyIndex > -1))) {
900                     // Stash the match
901                     filteredResults.push(oResult);
902                 }
903             }
904             
905             // Filter no more if maxResultsDisplayed is reached
906             if(len>nMax && filteredResults.length===nMax) {
907                 break;
908             }
909         }
910         oParsedResponse.results = filteredResults;
911     }
912     else {
913     }
914     
915     return oParsedResponse;
916 };
917
918 /**
919  * Handles response for display. This is the callback function method passed to
920  * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
921  * returned to the AutoComplete instance.
922  *
923  * @method handleResponse
924  * @param sQuery {String} Original request.
925  * @param oResponse {Object} Response object.
926  * @param oPayload {MIXED} (optional) Additional argument(s)
927  */
928 YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
929     if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
930         this._populateList(sQuery, oResponse, oPayload);
931     }
932 };
933
934 /**
935  * Overridable method called before container is loaded with result data.
936  *
937  * @method doBeforeLoadData
938  * @param sQuery {String} Original request.
939  * @param oResponse {Object} Response object.
940  * @param oPayload {MIXED} (optional) Additional argument(s)
941  * @return {Boolean} Return true to continue loading data, false to cancel.
942  */
943 YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
944     return true;
945 };
946
947 /**
948  * Overridable method that returns HTML markup for one result to be populated
949  * as innerHTML of an &lt;LI&gt; element. 
950  *
951  * @method formatResult
952  * @param oResultData {Object} Result data object.
953  * @param sQuery {String} The corresponding query string.
954  * @param sResultMatch {HTMLElement} The current query string. 
955  * @return {String} HTML markup of formatted result data.
956  */
957 YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
958     var sMarkup = (sResultMatch) ? sResultMatch : "";
959     return sMarkup;
960 };
961
962 /**
963  * Overridable method called before container expands allows implementers to access data
964  * and DOM elements.
965  *
966  * @method doBeforeExpandContainer
967  * @param elTextbox {HTMLElement} The text input box.
968  * @param elContainer {HTMLElement} The container element.
969  * @param sQuery {String} The query string.
970  * @param aResults {Object[]}  An array of query results.
971  * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
972  */
973 YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
974     return true;
975 };
976
977
978 /**
979  * Nulls out the entire AutoComplete instance and related objects, removes attached
980  * event listeners, and clears out DOM elements inside the container. After
981  * calling this method, the instance reference should be expliclitly nulled by
982  * implementer, as in myAutoComplete = null. Use with caution!
983  *
984  * @method destroy
985  */
986 YAHOO.widget.AutoComplete.prototype.destroy = function() {
987     var instanceName = this.toString();
988     var elInput = this._elTextbox;
989     var elContainer = this._elContainer;
990
991     // Unhook custom events
992     this.textboxFocusEvent.unsubscribeAll();
993     this.textboxKeyEvent.unsubscribeAll();
994     this.dataRequestEvent.unsubscribeAll();
995     this.dataReturnEvent.unsubscribeAll();
996     this.dataErrorEvent.unsubscribeAll();
997     this.containerPopulateEvent.unsubscribeAll();
998     this.containerExpandEvent.unsubscribeAll();
999     this.typeAheadEvent.unsubscribeAll();
1000     this.itemMouseOverEvent.unsubscribeAll();
1001     this.itemMouseOutEvent.unsubscribeAll();
1002     this.itemArrowToEvent.unsubscribeAll();
1003     this.itemArrowFromEvent.unsubscribeAll();
1004     this.itemSelectEvent.unsubscribeAll();
1005     this.unmatchedItemSelectEvent.unsubscribeAll();
1006     this.selectionEnforceEvent.unsubscribeAll();
1007     this.containerCollapseEvent.unsubscribeAll();
1008     this.textboxBlurEvent.unsubscribeAll();
1009     this.textboxChangeEvent.unsubscribeAll();
1010
1011     // Unhook DOM events
1012     YAHOO.util.Event.purgeElement(elInput, true);
1013     YAHOO.util.Event.purgeElement(elContainer, true);
1014
1015     // Remove DOM elements
1016     elContainer.innerHTML = "";
1017
1018     // Null out objects
1019     for(var key in this) {
1020         if(YAHOO.lang.hasOwnProperty(this, key)) {
1021             this[key] = null;
1022         }
1023     }
1024
1025 };
1026
1027 /////////////////////////////////////////////////////////////////////////////
1028 //
1029 // Public events
1030 //
1031 /////////////////////////////////////////////////////////////////////////////
1032
1033 /**
1034  * Fired when the input field receives focus.
1035  *
1036  * @event textboxFocusEvent
1037  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1038  */
1039 YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1040
1041 /**
1042  * Fired when the input field receives key input.
1043  *
1044  * @event textboxKeyEvent
1045  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1046  * @param nKeycode {Number} The keycode number.
1047  */
1048 YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1049
1050 /**
1051  * Fired when the AutoComplete instance makes a request to the DataSource.
1052  * 
1053  * @event dataRequestEvent
1054  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1055  * @param sQuery {String} The query string. 
1056  * @param oRequest {Object} The request.
1057  */
1058 YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1059
1060 /**
1061  * Fired when the AutoComplete instance receives query results from the data
1062  * source.
1063  *
1064  * @event dataReturnEvent
1065  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1066  * @param sQuery {String} The query string.
1067  * @param aResults {Object[]} Results array.
1068  */
1069 YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1070
1071 /**
1072  * Fired when the AutoComplete instance does not receive query results from the
1073  * DataSource due to an error.
1074  *
1075  * @event dataErrorEvent
1076  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1077  * @param sQuery {String} The query string.
1078  * @param oResponse {Object} The response object, if available.
1079  */
1080 YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1081
1082 /**
1083  * Fired when the results container is populated.
1084  *
1085  * @event containerPopulateEvent
1086  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1087  */
1088 YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1089
1090 /**
1091  * Fired when the results container is expanded.
1092  *
1093  * @event containerExpandEvent
1094  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1095  */
1096 YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1097
1098 /**
1099  * Fired when the input field has been prefilled by the type-ahead
1100  * feature. 
1101  *
1102  * @event typeAheadEvent
1103  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1104  * @param sQuery {String} The query string.
1105  * @param sPrefill {String} The prefill string.
1106  */
1107 YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1108
1109 /**
1110  * Fired when result item has been moused over.
1111  *
1112  * @event itemMouseOverEvent
1113  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1114  * @param elItem {HTMLElement} The &lt;li&gt element item moused to.
1115  */
1116 YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1117
1118 /**
1119  * Fired when result item has been moused out.
1120  *
1121  * @event itemMouseOutEvent
1122  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1123  * @param elItem {HTMLElement} The &lt;li&gt; element item moused from.
1124  */
1125 YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1126
1127 /**
1128  * Fired when result item has been arrowed to. 
1129  *
1130  * @event itemArrowToEvent
1131  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1132  * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed to.
1133  */
1134 YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1135
1136 /**
1137  * Fired when result item has been arrowed away from.
1138  *
1139  * @event itemArrowFromEvent
1140  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1141  * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed from.
1142  */
1143 YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1144
1145 /**
1146  * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1147  *
1148  * @event itemSelectEvent
1149  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1150  * @param elItem {HTMLElement} The selected &lt;li&gt; element item.
1151  * @param oData {Object} The data returned for the item, either as an object,
1152  * or mapped from the schema into an array.
1153  */
1154 YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1155
1156 /**
1157  * Fired when a user selection does not match any of the displayed result items.
1158  *
1159  * @event unmatchedItemSelectEvent
1160  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1161  * @param sSelection {String} The selected string.  
1162  */
1163 YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1164
1165 /**
1166  * Fired if forceSelection is enabled and the user's input has been cleared
1167  * because it did not match one of the returned query results.
1168  *
1169  * @event selectionEnforceEvent
1170  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1171  * @param sClearedValue {String} The cleared value (including delimiters if applicable). 
1172  */
1173 YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1174
1175 /**
1176  * Fired when the results container is collapsed.
1177  *
1178  * @event containerCollapseEvent
1179  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1180  */
1181 YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1182
1183 /**
1184  * Fired when the input field loses focus.
1185  *
1186  * @event textboxBlurEvent
1187  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1188  */
1189 YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1190
1191 /**
1192  * Fired when the input field value has changed when it loses focus.
1193  *
1194  * @event textboxChangeEvent
1195  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1196  */
1197 YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1198
1199 /////////////////////////////////////////////////////////////////////////////
1200 //
1201 // Private member variables
1202 //
1203 /////////////////////////////////////////////////////////////////////////////
1204
1205 /**
1206  * Internal class variable to index multiple AutoComplete instances.
1207  *
1208  * @property _nIndex
1209  * @type Number
1210  * @default 0
1211  * @private
1212  */
1213 YAHOO.widget.AutoComplete._nIndex = 0;
1214
1215 /**
1216  * Name of AutoComplete instance.
1217  *
1218  * @property _sName
1219  * @type String
1220  * @private
1221  */
1222 YAHOO.widget.AutoComplete.prototype._sName = null;
1223
1224 /**
1225  * Text input field DOM element.
1226  *
1227  * @property _elTextbox
1228  * @type HTMLElement
1229  * @private
1230  */
1231 YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1232
1233 /**
1234  * Container DOM element.
1235  *
1236  * @property _elContainer
1237  * @type HTMLElement
1238  * @private
1239  */
1240 YAHOO.widget.AutoComplete.prototype._elContainer = null;
1241
1242 /**
1243  * Reference to content element within container element.
1244  *
1245  * @property _elContent
1246  * @type HTMLElement
1247  * @private
1248  */
1249 YAHOO.widget.AutoComplete.prototype._elContent = null;
1250
1251 /**
1252  * Reference to header element within content element.
1253  *
1254  * @property _elHeader
1255  * @type HTMLElement
1256  * @private
1257  */
1258 YAHOO.widget.AutoComplete.prototype._elHeader = null;
1259
1260 /**
1261  * Reference to body element within content element.
1262  *
1263  * @property _elBody
1264  * @type HTMLElement
1265  * @private
1266  */
1267 YAHOO.widget.AutoComplete.prototype._elBody = null;
1268
1269 /**
1270  * Reference to footer element within content element.
1271  *
1272  * @property _elFooter
1273  * @type HTMLElement
1274  * @private
1275  */
1276 YAHOO.widget.AutoComplete.prototype._elFooter = null;
1277
1278 /**
1279  * Reference to shadow element within container element.
1280  *
1281  * @property _elShadow
1282  * @type HTMLElement
1283  * @private
1284  */
1285 YAHOO.widget.AutoComplete.prototype._elShadow = null;
1286
1287 /**
1288  * Reference to iframe element within container element.
1289  *
1290  * @property _elIFrame
1291  * @type HTMLElement
1292  * @private
1293  */
1294 YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1295
1296 /**
1297  * Whether or not the widget instance is currently active. If query results come back
1298  * but the user has already moved on, do not proceed with auto complete behavior.
1299  *
1300  * @property _bFocused
1301  * @type Boolean
1302  * @private
1303  */
1304 YAHOO.widget.AutoComplete.prototype._bFocused = false;
1305
1306 /**
1307  * Animation instance for container expand/collapse.
1308  *
1309  * @property _oAnim
1310  * @type Boolean
1311  * @private
1312  */
1313 YAHOO.widget.AutoComplete.prototype._oAnim = null;
1314
1315 /**
1316  * Whether or not the results container is currently open.
1317  *
1318  * @property _bContainerOpen
1319  * @type Boolean
1320  * @private
1321  */
1322 YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1323
1324 /**
1325  * Whether or not the mouse is currently over the results
1326  * container. This is necessary in order to prevent clicks on container items
1327  * from being text input field blur events.
1328  *
1329  * @property _bOverContainer
1330  * @type Boolean
1331  * @private
1332  */
1333 YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1334
1335 /**
1336  * Internal reference to &lt;ul&gt; elements that contains query results within the
1337  * results container.
1338  *
1339  * @property _elList
1340  * @type HTMLElement
1341  * @private
1342  */
1343 YAHOO.widget.AutoComplete.prototype._elList = null;
1344
1345 /*
1346  * Array of &lt;li&gt; elements references that contain query results within the
1347  * results container.
1348  *
1349  * @property _aListItemEls
1350  * @type HTMLElement[]
1351  * @private
1352  */
1353 //YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1354
1355 /**
1356  * Number of &lt;li&gt; elements currently displayed in results container.
1357  *
1358  * @property _nDisplayedItems
1359  * @type Number
1360  * @private
1361  */
1362 YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1363
1364 /*
1365  * Internal count of &lt;li&gt; elements displayed and hidden in results container.
1366  *
1367  * @property _maxResultsDisplayed
1368  * @type Number
1369  * @private
1370  */
1371 //YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1372
1373 /**
1374  * Current query string
1375  *
1376  * @property _sCurQuery
1377  * @type String
1378  * @private
1379  */
1380 YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1381
1382 /**
1383  * Selections from previous queries (for saving delimited queries).
1384  *
1385  * @property _sPastSelections
1386  * @type String
1387  * @default "" 
1388  * @private
1389  */
1390 YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1391
1392 /**
1393  * Stores initial input value used to determine if textboxChangeEvent should be fired.
1394  *
1395  * @property _sInitInputValue
1396  * @type String
1397  * @private
1398  */
1399 YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1400
1401 /**
1402  * Pointer to the currently highlighted &lt;li&gt; element in the container.
1403  *
1404  * @property _elCurListItem
1405  * @type HTMLElement
1406  * @private
1407  */
1408 YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1409
1410 /**
1411  * Pointer to the currently pre-highlighted &lt;li&gt; element in the container.
1412  *
1413  * @property _elCurPrehighlightItem
1414  * @type HTMLElement
1415  * @private
1416  */
1417 YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem = null;
1418
1419 /**
1420  * Whether or not an item has been selected since the container was populated
1421  * with results. Reset to false by _populateList, and set to true when item is
1422  * selected.
1423  *
1424  * @property _bItemSelected
1425  * @type Boolean
1426  * @private
1427  */
1428 YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1429
1430 /**
1431  * Key code of the last key pressed in textbox.
1432  *
1433  * @property _nKeyCode
1434  * @type Number
1435  * @private
1436  */
1437 YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1438
1439 /**
1440  * Delay timeout ID.
1441  *
1442  * @property _nDelayID
1443  * @type Number
1444  * @private
1445  */
1446 YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1447
1448 /**
1449  * TypeAhead delay timeout ID.
1450  *
1451  * @property _nTypeAheadDelayID
1452  * @type Number
1453  * @private
1454  */
1455 YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1456
1457 /**
1458  * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1459  * as well.
1460  *
1461  * @property _iFrameSrc
1462  * @type String
1463  * @private
1464  */
1465 YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1466
1467 /**
1468  * For users typing via certain IMEs, queries must be triggered by intervals,
1469  * since key events yet supported across all browsers for all IMEs.
1470  *
1471  * @property _queryInterval
1472  * @type Object
1473  * @private
1474  */
1475 YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1476
1477 /**
1478  * Internal tracker to last known textbox value, used to determine whether or not
1479  * to trigger a query via interval for certain IME users.
1480  *
1481  * @event _sLastTextboxValue
1482  * @type String
1483  * @private
1484  */
1485 YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1486
1487 /////////////////////////////////////////////////////////////////////////////
1488 //
1489 // Private methods
1490 //
1491 /////////////////////////////////////////////////////////////////////////////
1492
1493 /**
1494  * Updates and validates latest public config properties.
1495  *
1496  * @method __initProps
1497  * @private
1498  */
1499 YAHOO.widget.AutoComplete.prototype._initProps = function() {
1500     // Correct any invalid values
1501     var minQueryLength = this.minQueryLength;
1502     if(!YAHOO.lang.isNumber(minQueryLength)) {
1503         this.minQueryLength = 1;
1504     }
1505     var maxResultsDisplayed = this.maxResultsDisplayed;
1506     if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1507         this.maxResultsDisplayed = 10;
1508     }
1509     var queryDelay = this.queryDelay;
1510     if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1511         this.queryDelay = 0.2;
1512     }
1513     var typeAheadDelay = this.typeAheadDelay;
1514     if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1515         this.typeAheadDelay = 0.2;
1516     }
1517     var delimChar = this.delimChar;
1518     if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1519         this.delimChar = [delimChar];
1520     }
1521     else if(!YAHOO.lang.isArray(delimChar)) {
1522         this.delimChar = null;
1523     }
1524     var animSpeed = this.animSpeed;
1525     if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
1526         if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
1527             this.animSpeed = 0.3;
1528         }
1529         if(!this._oAnim ) {
1530             this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1531         }
1532         else {
1533             this._oAnim.duration = this.animSpeed;
1534         }
1535     }
1536     if(this.forceSelection && delimChar) {
1537     }
1538 };
1539
1540 /**
1541  * Initializes the results container helpers if they are enabled and do
1542  * not exist
1543  *
1544  * @method _initContainerHelperEls
1545  * @private
1546  */
1547 YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
1548     if(this.useShadow && !this._elShadow) {
1549         var elShadow = document.createElement("div");
1550         elShadow.className = "yui-ac-shadow";
1551         elShadow.style.width = 0;
1552         elShadow.style.height = 0;
1553         this._elShadow = this._elContainer.appendChild(elShadow);
1554     }
1555     if(this.useIFrame && !this._elIFrame) {
1556         var elIFrame = document.createElement("iframe");
1557         elIFrame.src = this._iFrameSrc;
1558         elIFrame.frameBorder = 0;
1559         elIFrame.scrolling = "no";
1560         elIFrame.style.position = "absolute";
1561         elIFrame.style.width = 0;
1562         elIFrame.style.height = 0;
1563         elIFrame.style.padding = 0;
1564         elIFrame.tabIndex = -1;
1565         elIFrame.role = "presentation";
1566         elIFrame.title = "Presentational iframe shim";
1567         this._elIFrame = this._elContainer.appendChild(elIFrame);
1568     }
1569 };
1570
1571 /**
1572  * Initializes the results container once at object creation
1573  *
1574  * @method _initContainerEl
1575  * @private
1576  */
1577 YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1578     YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1579     
1580     if(!this._elContent) {
1581         // The elContent div is assigned DOM listeners and 
1582         // helps size the iframe and shadow properly
1583         var elContent = document.createElement("div");
1584         elContent.className = "yui-ac-content";
1585         elContent.style.display = "none";
1586
1587         this._elContent = this._elContainer.appendChild(elContent);
1588
1589         var elHeader = document.createElement("div");
1590         elHeader.className = "yui-ac-hd";
1591         elHeader.style.display = "none";
1592         this._elHeader = this._elContent.appendChild(elHeader);
1593
1594         var elBody = document.createElement("div");
1595         elBody.className = "yui-ac-bd";
1596         this._elBody = this._elContent.appendChild(elBody);
1597
1598         var elFooter = document.createElement("div");
1599         elFooter.className = "yui-ac-ft";
1600         elFooter.style.display = "none";
1601         this._elFooter = this._elContent.appendChild(elFooter);
1602     }
1603     else {
1604     }
1605 };
1606
1607 /**
1608  * Clears out contents of container body and creates up to
1609  * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1610  * &lt;ul&gt; element.
1611  *
1612  * @method _initListEl
1613  * @private
1614  */
1615 YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1616     var nListLength = this.maxResultsDisplayed,
1617         elList = this._elList || document.createElement("ul"),
1618         elListItem;
1619     
1620     while(elList.childNodes.length < nListLength) {
1621         elListItem = document.createElement("li");
1622         elListItem.style.display = "none";
1623         elListItem._nItemIndex = elList.childNodes.length;
1624         elList.appendChild(elListItem);
1625     }
1626     if(!this._elList) {
1627         var elBody = this._elBody;
1628         YAHOO.util.Event.purgeElement(elBody, true);
1629         elBody.innerHTML = "";
1630         this._elList = elBody.appendChild(elList);
1631     }
1632     
1633     this._elBody.style.display = "";
1634 };
1635
1636 /**
1637  * Focuses input field.
1638  *
1639  * @method _focus
1640  * @private
1641  */
1642 YAHOO.widget.AutoComplete.prototype._focus = function() {
1643     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1644     var oSelf = this;
1645     setTimeout(function() {
1646         try {
1647             oSelf._elTextbox.focus();
1648         }
1649         catch(e) {
1650         }
1651     },0);
1652 };
1653
1654 /**
1655  * Enables interval detection for IME support.
1656  *
1657  * @method _enableIntervalDetection
1658  * @private
1659  */
1660 YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1661     var oSelf = this;
1662     if(!oSelf._queryInterval && oSelf.queryInterval) {
1663         oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1664     }
1665 };
1666
1667 /**
1668  * Enables interval detection for a less performant but brute force mechanism to
1669  * detect input values at an interval set by queryInterval and send queries if
1670  * input value has changed. Needed to support right-click+paste or shift+insert
1671  * edge cases. Please note that intervals are cleared at the end of each interaction,
1672  * so enableIntervalDetection must be called for each new interaction. The
1673  * recommended approach is to call it in response to textboxFocusEvent.
1674  *
1675  * @method enableIntervalDetection
1676  */
1677 YAHOO.widget.AutoComplete.prototype.enableIntervalDetection =
1678     YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;
1679
1680 /**
1681  * Enables query triggers based on text input detection by intervals (rather
1682  * than by key events).
1683  *
1684  * @method _onInterval
1685  * @private
1686  */
1687 YAHOO.widget.AutoComplete.prototype._onInterval = function() {
1688     var currValue = this._elTextbox.value;
1689     var lastValue = this._sLastTextboxValue;
1690     if(currValue != lastValue) {
1691         this._sLastTextboxValue = currValue;
1692         this._sendQuery(currValue);
1693     }
1694 };
1695
1696 /**
1697  * Cancels text input detection by intervals.
1698  *
1699  * @method _clearInterval
1700  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1701  * @private
1702  */
1703 YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
1704     if(this._queryInterval) {
1705         clearInterval(this._queryInterval);
1706         this._queryInterval = null;
1707     }
1708 };
1709
1710 /**
1711  * Whether or not key is functional or should be ignored. Note that the right
1712  * arrow key is NOT an ignored key since it triggers queries for certain intl
1713  * charsets.
1714  *
1715  * @method _isIgnoreKey
1716  * @param nKeycode {Number} Code of key pressed.
1717  * @return {Boolean} True if key should be ignored, false otherwise.
1718  * @private
1719  */
1720 YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1721     if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
1722             (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1723             (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
1724             (nKeyCode == 27) || // esc
1725             (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1726             /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1727             (nKeyCode == 40) || // down*/
1728             (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1729             (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
1730             (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
1731         ) { 
1732         return true;
1733     }
1734     return false;
1735 };
1736
1737 /**
1738  * Makes query request to the DataSource.
1739  *
1740  * @method _sendQuery
1741  * @param sQuery {String} Query string.
1742  * @private
1743  */
1744 YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1745     // Widget has been effectively turned off
1746     if(this.minQueryLength < 0) {
1747         this._toggleContainer(false);
1748         return;
1749     }
1750     // Delimiter has been enabled
1751     if(this.delimChar) {
1752         var extraction = this._extractQuery(sQuery);
1753         // Here is the query itself
1754         sQuery = extraction.query;
1755         // ...and save the rest of the string for later
1756         this._sPastSelections = extraction.previous;
1757     }
1758
1759     // Don't search queries that are too short
1760     if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1761         if(this._nDelayID != -1) {
1762             clearTimeout(this._nDelayID);
1763         }
1764         this._toggleContainer(false);
1765         return;
1766     }
1767
1768     sQuery = encodeURIComponent(sQuery);
1769     this._nDelayID = -1;    // Reset timeout ID because request is being made
1770     
1771     // Subset matching
1772     if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1773         var oResponse = this.getSubsetMatches(sQuery);
1774         if(oResponse) {
1775             this.handleResponse(sQuery, oResponse, {query: sQuery});
1776             return;
1777         }
1778     }
1779     
1780     if(this.dataSource.responseStripAfter) {
1781         this.dataSource.doBeforeParseData = this.preparseRawResponse;
1782     }
1783     if(this.applyLocalFilter) {
1784         this.dataSource.doBeforeCallback = this.filterResults;
1785     }
1786     
1787     var sRequest = this.generateRequest(sQuery);
1788     this.dataRequestEvent.fire(this, sQuery, sRequest);
1789
1790     this.dataSource.sendRequest(sRequest, {
1791             success : this.handleResponse,
1792             failure : this.handleResponse,
1793             scope   : this,
1794             argument: {
1795                 query: sQuery
1796             }
1797     });
1798 };
1799
1800 /**
1801  * Populates the given &lt;li&gt; element with return value from formatResult().
1802  *
1803  * @method _populateListItem
1804  * @param elListItem {HTMLElement} The LI element.
1805  * @param oResult {Object} The result object.
1806  * @param sCurQuery {String} The query string.
1807  * @private
1808  */
1809 YAHOO.widget.AutoComplete.prototype._populateListItem = function(elListItem, oResult, sQuery) {
1810     elListItem.innerHTML = this.formatResult(oResult, sQuery, elListItem._sResultMatch);
1811 };
1812
1813 /**
1814  * Populates the array of &lt;li&gt; elements in the container with query
1815  * results.
1816  *
1817  * @method _populateList
1818  * @param sQuery {String} Original request.
1819  * @param oResponse {Object} Response object.
1820  * @param oPayload {MIXED} (optional) Additional argument(s)
1821  * @private
1822  */
1823 YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1824     // Clear previous timeout
1825     if(this._nTypeAheadDelayID != -1) {
1826         clearTimeout(this._nTypeAheadDelayID);
1827     }
1828         
1829     sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1830     
1831     // Pass data through abstract method for any transformations
1832     var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1833
1834     // Data is ok
1835     if(ok && !oResponse.error) {
1836         this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1837         
1838         // Continue only if instance is still active (i.e., user hasn't already moved on)
1839         if(this._bFocused) {
1840             // Store state for this interaction
1841             var sCurQuery = decodeURIComponent(sQuery);
1842             this._sCurQuery = sCurQuery;
1843             this._bItemSelected = false;
1844         
1845             var allResults = oResponse.results,
1846                 nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
1847                 sMatchKey = (this.dataSource.responseSchema.fields) ? 
1848                     (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
1849             
1850             if(nItemsToShow > 0) {
1851                 // Make sure container and helpers are ready to go
1852                 if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1853                     this._initListEl();
1854                 }
1855                 this._initContainerHelperEls();
1856                 
1857                 var allListItemEls = this._elList.childNodes;
1858                 // Fill items with data from the bottom up
1859                 for(var i = nItemsToShow-1; i >= 0; i--) {
1860                     var elListItem = allListItemEls[i],
1861                     oResult = allResults[i];
1862                     
1863                     // Backward compatibility
1864                     if(this.resultTypeList) {
1865                         // Results need to be converted back to an array
1866                         var aResult = [];
1867                         // Match key is first
1868                         aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
1869                         // Add additional data to the result array
1870                         var fields = this.dataSource.responseSchema.fields;
1871                         if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
1872                             for(var k=1, len=fields.length; k<len; k++) {
1873                                 aResult[aResult.length] = oResult[fields[k].key || fields[k]];
1874                             }
1875                         }
1876                         // No specific fields defined, so pass along entire data object
1877                         else {
1878                             // Already an array
1879                             if(YAHOO.lang.isArray(oResult)) {
1880                                 aResult = oResult;
1881                             }
1882                             // Simple string 
1883                             else if(YAHOO.lang.isString(oResult)) {
1884                                 aResult = [oResult];
1885                             }
1886                             // Object
1887                             else {
1888                                 aResult[1] = oResult;
1889                             }
1890                         }
1891                         oResult = aResult;
1892                     }
1893
1894                     // The matching value, including backward compatibility for array format and safety net
1895                     elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
1896                     elListItem._oResultData = oResult; // Additional data
1897                     this._populateListItem(elListItem, oResult, sCurQuery);
1898                     elListItem.style.display = "";
1899                 }
1900         
1901                 // Clear out extraneous items
1902                 if(nItemsToShow < allListItemEls.length) {
1903                     var extraListItem;
1904                     for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1905                         extraListItem = allListItemEls[j];
1906                         extraListItem.style.display = "none";
1907                     }
1908                 }
1909                 
1910                 this._nDisplayedItems = nItemsToShow;
1911                 
1912                 this.containerPopulateEvent.fire(this, sQuery, allResults);
1913                 
1914                 // Highlight the first item
1915                 if(this.autoHighlight) {
1916                     var elFirstListItem = this._elList.firstChild;
1917                     this._toggleHighlight(elFirstListItem,"to");
1918                     this.itemArrowToEvent.fire(this, elFirstListItem);
1919                     this._typeAhead(elFirstListItem,sQuery);
1920                 }
1921                 // Unhighlight any previous time
1922                 else {
1923                     this._toggleHighlight(this._elCurListItem,"from");
1924                 }
1925         
1926                 // Pre-expansion stuff
1927                 ok = this._doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
1928                 
1929                 // Expand the container
1930                 this._toggleContainer(ok);
1931             }
1932             else {
1933                 this._toggleContainer(false);
1934             }
1935
1936             return;
1937         }
1938     }
1939     // Error
1940     else {
1941         this.dataErrorEvent.fire(this, sQuery, oResponse);
1942     }
1943         
1944 };
1945
1946 /**
1947  * Called before container expands, by default snaps container to the
1948  * bottom-left corner of the input element, then calls public overrideable method.
1949  *
1950  * @method _doBeforeExpandContainer
1951  * @param elTextbox {HTMLElement} The text input box.
1952  * @param elContainer {HTMLElement} The container element.
1953  * @param sQuery {String} The query string.
1954  * @param aResults {Object[]}  An array of query results.
1955  * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
1956  * @private 
1957  */
1958 YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
1959     if(this.autoSnapContainer) {
1960         this.snapContainer();
1961     }
1962
1963     return this.doBeforeExpandContainer(elTextbox, elContainer, sQuery, aResults);
1964 };
1965
1966 /**
1967  * When forceSelection is true and the user attempts
1968  * leave the text input box without selecting an item from the query results,
1969  * the user selection is cleared.
1970  *
1971  * @method _clearSelection
1972  * @private
1973  */
1974 YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
1975     var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
1976             {previous:"",query:this._elTextbox.value};
1977     this._elTextbox.value = extraction.previous;
1978     this.selectionEnforceEvent.fire(this, extraction.query);
1979 };
1980
1981 /**
1982  * Whether or not user-typed value in the text input box matches any of the
1983  * query results.
1984  *
1985  * @method _textMatchesOption
1986  * @return {HTMLElement} Matching list item element if user-input text matches
1987  * a result, null otherwise.
1988  * @private
1989  */
1990 YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
1991     var elMatch = null;
1992
1993     for(var i=0; i<this._nDisplayedItems; i++) {
1994         var elListItem = this._elList.childNodes[i];
1995         var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
1996         if(sMatch == this._sCurQuery.toLowerCase()) {
1997             elMatch = elListItem;
1998             break;
1999         }
2000     }
2001     return(elMatch);
2002 };
2003
2004 /**
2005  * Updates in the text input box with the first query result as the user types,
2006  * selecting the substring that the user has not typed.
2007  *
2008  * @method _typeAhead
2009  * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
2010  * @param sQuery {String} Query string.
2011  * @private
2012  */
2013 YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
2014     // Don't typeAhead if turned off or is backspace
2015     if(!this.typeAhead || (this._nKeyCode == 8)) {
2016         return;
2017     }
2018
2019     var oSelf = this,
2020         elTextbox = this._elTextbox;
2021         
2022     // Only if text selection is supported
2023     if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
2024         // Set and store timeout for this typeahead
2025         this._nTypeAheadDelayID = setTimeout(function() {
2026                 // Select the portion of text that the user has not typed
2027                 var nStart = elTextbox.value.length; // any saved queries plus what user has typed
2028                 oSelf._updateValue(elListItem);
2029                 var nEnd = elTextbox.value.length;
2030                 oSelf._selectText(elTextbox,nStart,nEnd);
2031                 var sPrefill = elTextbox.value.substr(nStart,nEnd);
2032                 oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
2033             },(this.typeAheadDelay*1000));            
2034     }
2035 };
2036
2037 /**
2038  * Selects text in the input field.
2039  *
2040  * @method _selectText
2041  * @param elTextbox {HTMLElement} Text input box element in which to select text.
2042  * @param nStart {Number} Starting index of text string to select.
2043  * @param nEnd {Number} Ending index of text selection.
2044  * @private
2045  */
2046 YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
2047     if(elTextbox.setSelectionRange) { // For Mozilla
2048         elTextbox.setSelectionRange(nStart,nEnd);
2049     }
2050     else if(elTextbox.createTextRange) { // For IE
2051         var oTextRange = elTextbox.createTextRange();
2052         oTextRange.moveStart("character", nStart);
2053         oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
2054         oTextRange.select();
2055     }
2056     else {
2057         elTextbox.select();
2058     }
2059 };
2060
2061 /**
2062  * Extracts rightmost query from delimited string.
2063  *
2064  * @method _extractQuery
2065  * @param sQuery {String} String to parse
2066  * @return {Object} Object literal containing properties "query" and "previous".  
2067  * @private
2068  */
2069 YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
2070     var aDelimChar = this.delimChar,
2071         nDelimIndex = -1,
2072         nNewIndex, nQueryStart,
2073         i = aDelimChar.length-1,
2074         sPrevious;
2075         
2076     // Loop through all possible delimiters and find the rightmost one in the query
2077     // A " " may be a false positive if they are defined as delimiters AND
2078     // are used to separate delimited queries
2079     for(; i >= 0; i--) {
2080         nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
2081         if(nNewIndex > nDelimIndex) {
2082             nDelimIndex = nNewIndex;
2083         }
2084     }
2085     // If we think the last delimiter is a space (" "), make sure it is NOT
2086     // a false positive by also checking the char directly before it
2087     if(aDelimChar[i] == " ") {
2088         for (var j = aDelimChar.length-1; j >= 0; j--) {
2089             if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
2090                 nDelimIndex--;
2091                 break;
2092             }
2093         }
2094     }
2095     // A delimiter has been found in the query so extract the latest query from past selections
2096     if(nDelimIndex > -1) {
2097         nQueryStart = nDelimIndex + 1;
2098         // Trim any white space from the beginning...
2099         while(sQuery.charAt(nQueryStart) == " ") {
2100             nQueryStart += 1;
2101         }
2102         // ...and save the rest of the string for later
2103         sPrevious = sQuery.substring(0,nQueryStart);
2104         // Here is the query itself
2105         sQuery = sQuery.substr(nQueryStart);
2106     }
2107     // No delimiter found in the query, so there are no selections from past queries
2108     else {
2109         sPrevious = "";
2110     }
2111     
2112     return {
2113         previous: sPrevious,
2114         query: sQuery
2115     };
2116 };
2117
2118 /**
2119  * Syncs results container with its helpers.
2120  *
2121  * @method _toggleContainerHelpers
2122  * @param bShow {Boolean} True if container is expanded, false if collapsed
2123  * @private
2124  */
2125 YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2126     var width = this._elContent.offsetWidth + "px";
2127     var height = this._elContent.offsetHeight + "px";
2128
2129     if(this.useIFrame && this._elIFrame) {
2130     var elIFrame = this._elIFrame;
2131         if(bShow) {
2132             elIFrame.style.width = width;
2133             elIFrame.style.height = height;
2134             elIFrame.style.padding = "";
2135         }
2136         else {
2137             elIFrame.style.width = 0;
2138             elIFrame.style.height = 0;
2139             elIFrame.style.padding = 0;
2140         }
2141     }
2142     if(this.useShadow && this._elShadow) {
2143     var elShadow = this._elShadow;
2144         if(bShow) {
2145             elShadow.style.width = width;
2146             elShadow.style.height = height;
2147         }
2148         else {
2149             elShadow.style.width = 0;
2150             elShadow.style.height = 0;
2151         }
2152     }
2153 };
2154
2155 /**
2156  * Animates expansion or collapse of the container.
2157  *
2158  * @method _toggleContainer
2159  * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2160  * @private
2161  */
2162 YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2163
2164     var elContainer = this._elContainer;
2165
2166     // If implementer has container always open and it's already open, don't mess with it
2167     // Container is initialized with display "none" so it may need to be shown first time through
2168     if(this.alwaysShowContainer && this._bContainerOpen) {
2169         return;
2170     }
2171     
2172     // Reset states
2173     if(!bShow) {
2174         this._toggleHighlight(this._elCurListItem,"from");
2175         this._nDisplayedItems = 0;
2176         this._sCurQuery = null;
2177         
2178         // Container is already closed, so don't bother with changing the UI
2179         if(this._elContent.style.display == "none") {
2180             return;
2181         }
2182     }
2183
2184     // If animation is enabled...
2185     var oAnim = this._oAnim;
2186     if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2187         if(oAnim.isAnimated()) {
2188             oAnim.stop(true);
2189         }
2190
2191         // Clone container to grab current size offscreen
2192         var oClone = this._elContent.cloneNode(true);
2193         elContainer.appendChild(oClone);
2194         oClone.style.top = "-9000px";
2195         oClone.style.width = "";
2196         oClone.style.height = "";
2197         oClone.style.display = "";
2198
2199         // Current size of the container is the EXPANDED size
2200         var wExp = oClone.offsetWidth;
2201         var hExp = oClone.offsetHeight;
2202
2203         // Calculate COLLAPSED sizes based on horiz and vert anim
2204         var wColl = (this.animHoriz) ? 0 : wExp;
2205         var hColl = (this.animVert) ? 0 : hExp;
2206
2207         // Set animation sizes
2208         oAnim.attributes = (bShow) ?
2209             {width: { to: wExp }, height: { to: hExp }} :
2210             {width: { to: wColl}, height: { to: hColl }};
2211
2212         // If opening anew, set to a collapsed size...
2213         if(bShow && !this._bContainerOpen) {
2214             this._elContent.style.width = wColl+"px";
2215             this._elContent.style.height = hColl+"px";
2216         }
2217         // Else, set it to its last known size.
2218         else {
2219             this._elContent.style.width = wExp+"px";
2220             this._elContent.style.height = hExp+"px";
2221         }
2222
2223         elContainer.removeChild(oClone);
2224         oClone = null;
2225
2226         var oSelf = this;
2227         var onAnimComplete = function() {
2228             // Finish the collapse
2229                 oAnim.onComplete.unsubscribeAll();
2230
2231             if(bShow) {
2232                 oSelf._toggleContainerHelpers(true);
2233                 oSelf._bContainerOpen = bShow;
2234                 oSelf.containerExpandEvent.fire(oSelf);
2235             }
2236             else {
2237                 oSelf._elContent.style.display = "none";
2238                 oSelf._bContainerOpen = bShow;
2239                 oSelf.containerCollapseEvent.fire(oSelf);
2240             }
2241         };
2242
2243         // Display container and animate it
2244         this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
2245         this._elContent.style.display = "";
2246         oAnim.onComplete.subscribe(onAnimComplete);
2247         oAnim.animate();
2248     }
2249     // Else don't animate, just show or hide
2250     else {
2251         if(bShow) {
2252             this._elContent.style.display = "";
2253             this._toggleContainerHelpers(true);
2254             this._bContainerOpen = bShow;
2255             this.containerExpandEvent.fire(this);
2256         }
2257         else {
2258             this._toggleContainerHelpers(false);
2259             this._elContent.style.display = "none";
2260             this._bContainerOpen = bShow;
2261             this.containerCollapseEvent.fire(this);
2262         }
2263    }
2264
2265 };
2266
2267 /**
2268  * Toggles the highlight on or off for an item in the container, and also cleans
2269  * up highlighting of any previous item.
2270  *
2271  * @method _toggleHighlight
2272  * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2273  * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2274  * @private
2275  */
2276 YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2277     if(elNewListItem) {
2278         var sHighlight = this.highlightClassName;
2279         if(this._elCurListItem) {
2280             // Remove highlight from old item
2281             YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
2282             this._elCurListItem = null;
2283         }
2284     
2285         if((sType == "to") && sHighlight) {
2286             // Apply highlight to new item
2287             YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2288             this._elCurListItem = elNewListItem;
2289         }
2290     }
2291 };
2292
2293 /**
2294  * Toggles the pre-highlight on or off for an item in the container, and also cleans
2295  * up pre-highlighting of any previous item.
2296  *
2297  * @method _togglePrehighlight
2298  * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2299  * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2300  * @private
2301  */
2302 YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2303     var sPrehighlight = this.prehighlightClassName;
2304
2305     if(this._elCurPrehighlightItem) {
2306         YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem, sPrehighlight);
2307     }
2308     if(elNewListItem == this._elCurListItem) {
2309         return;
2310     }
2311
2312     if((sType == "mouseover") && sPrehighlight) {
2313         // Apply prehighlight to new item
2314         YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2315         this._elCurPrehighlightItem = elNewListItem;
2316     }
2317     else {
2318         // Remove prehighlight from old item
2319         YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2320     }
2321 };
2322
2323 /**
2324  * Updates the text input box value with selected query result. If a delimiter
2325  * has been defined, then the value gets appended with the delimiter.
2326  *
2327  * @method _updateValue
2328  * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
2329  * @private
2330  */
2331 YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
2332     if(!this.suppressInputUpdate) {    
2333         var elTextbox = this._elTextbox;
2334         var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
2335         var sResultMatch = elListItem._sResultMatch;
2336     
2337         // Calculate the new value
2338         var sNewValue = "";
2339         if(sDelimChar) {
2340             // Preserve selections from past queries
2341             sNewValue = this._sPastSelections;
2342             // Add new selection plus delimiter
2343             sNewValue += sResultMatch + sDelimChar;
2344             if(sDelimChar != " ") {
2345                 sNewValue += " ";
2346             }
2347         }
2348         else { 
2349             sNewValue = sResultMatch;
2350         }
2351         
2352         // Update input field
2353         elTextbox.value = sNewValue;
2354     
2355         // Scroll to bottom of textarea if necessary
2356         if(elTextbox.type == "textarea") {
2357             elTextbox.scrollTop = elTextbox.scrollHeight;
2358         }
2359     
2360         // Move cursor to end
2361         var end = elTextbox.value.length;
2362         this._selectText(elTextbox,end,end);
2363     
2364         this._elCurListItem = elListItem;
2365     }
2366 };
2367
2368 /**
2369  * Selects a result item from the container
2370  *
2371  * @method _selectItem
2372  * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
2373  * @private
2374  */
2375 YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
2376     this._bItemSelected = true;
2377     this._updateValue(elListItem);
2378     this._sPastSelections = this._elTextbox.value;
2379     this._clearInterval();
2380     this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
2381     this._toggleContainer(false);
2382 };
2383
2384 /**
2385  * If an item is highlighted in the container, the right arrow key jumps to the
2386  * end of the textbox and selects the highlighted item, otherwise the container
2387  * is closed.
2388  *
2389  * @method _jumpSelection
2390  * @private
2391  */
2392 YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2393     if(this._elCurListItem) {
2394         this._selectItem(this._elCurListItem);
2395     }
2396     else {
2397         this._toggleContainer(false);
2398     }
2399 };
2400
2401 /**
2402  * Triggered by up and down arrow keys, changes the current highlighted
2403  * &lt;li&gt; element item. Scrolls container if necessary.
2404  *
2405  * @method _moveSelection
2406  * @param nKeyCode {Number} Code of key pressed.
2407  * @private
2408  */
2409 YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2410     if(this._bContainerOpen) {
2411         // Determine current item's id number
2412         var elCurListItem = this._elCurListItem,
2413             nCurItemIndex = -1;
2414
2415         if(elCurListItem) {
2416             nCurItemIndex = elCurListItem._nItemIndex;
2417         }
2418
2419         var nNewItemIndex = (nKeyCode == 40) ?
2420                 (nCurItemIndex + 1) : (nCurItemIndex - 1);
2421
2422         // Out of bounds
2423         if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2424             return;
2425         }
2426
2427         if(elCurListItem) {
2428             // Unhighlight current item
2429             this._toggleHighlight(elCurListItem, "from");
2430             this.itemArrowFromEvent.fire(this, elCurListItem);
2431         }
2432         if(nNewItemIndex == -1) {
2433            // Go back to query (remove type-ahead string)
2434             if(this.delimChar) {
2435                 this._elTextbox.value = this._sPastSelections + this._sCurQuery;
2436             }
2437             else {
2438                 this._elTextbox.value = this._sCurQuery;
2439             }
2440             return;
2441         }
2442         if(nNewItemIndex == -2) {
2443             // Close container
2444             this._toggleContainer(false);
2445             return;
2446         }
2447         
2448         var elNewListItem = this._elList.childNodes[nNewItemIndex],
2449
2450         // Scroll the container if necessary
2451             elContent = this._elContent,
2452             sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
2453             sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
2454             scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
2455         if(scrollOn && (nNewItemIndex > -1) &&
2456         (nNewItemIndex < this._nDisplayedItems)) {
2457             // User is keying down
2458             if(nKeyCode == 40) {
2459                 // Bottom of selected item is below scroll area...
2460                 if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
2461                     // Set bottom of scroll area to bottom of selected item
2462                     elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2463                 }
2464                 // Bottom of selected item is above scroll area...
2465                 else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < elContent.scrollTop) {
2466                     // Set top of selected item to top of scroll area
2467                     elContent.scrollTop = elNewListItem.offsetTop;
2468
2469                 }
2470             }
2471             // User is keying up
2472             else {
2473                 // Top of selected item is above scroll area
2474                 if(elNewListItem.offsetTop < elContent.scrollTop) {
2475                     // Set top of scroll area to top of selected item
2476                     this._elContent.scrollTop = elNewListItem.offsetTop;
2477                 }
2478                 // Top of selected item is below scroll area
2479                 else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
2480                     // Set bottom of selected item to bottom of scroll area
2481                     this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2482                 }
2483             }
2484         }
2485
2486         this._toggleHighlight(elNewListItem, "to");
2487         this.itemArrowToEvent.fire(this, elNewListItem);
2488         if(this.typeAhead) {
2489             this._updateValue(elNewListItem);
2490         }
2491     }
2492 };
2493
2494 /////////////////////////////////////////////////////////////////////////////
2495 //
2496 // Private event handlers
2497 //
2498 /////////////////////////////////////////////////////////////////////////////
2499
2500 /**
2501  * Handles container mouseover events.
2502  *
2503  * @method _onContainerMouseover
2504  * @param v {HTMLEvent} The mouseover event.
2505  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2506  * @private
2507  */
2508 YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
2509     var elTarget = YAHOO.util.Event.getTarget(v);
2510     var elTag = elTarget.nodeName.toLowerCase();
2511     while(elTarget && (elTag != "table")) {
2512         switch(elTag) {
2513             case "body":
2514                 return;
2515             case "li":
2516                 if(oSelf.prehighlightClassName) {
2517                     oSelf._togglePrehighlight(elTarget,"mouseover");
2518                 }
2519                 else {
2520                     oSelf._toggleHighlight(elTarget,"to");
2521                 }
2522             
2523                 oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2524                 break;
2525             case "div":
2526                 if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2527                     oSelf._bOverContainer = true;
2528                     return;
2529                 }
2530                 break;
2531             default:
2532                 break;
2533         }
2534         
2535         elTarget = elTarget.parentNode;
2536         if(elTarget) {
2537             elTag = elTarget.nodeName.toLowerCase();
2538         }
2539     }
2540 };
2541
2542 /**
2543  * Handles container mouseout events.
2544  *
2545  * @method _onContainerMouseout
2546  * @param v {HTMLEvent} The mouseout event.
2547  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2548  * @private
2549  */
2550 YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
2551     var elTarget = YAHOO.util.Event.getTarget(v);
2552     var elTag = elTarget.nodeName.toLowerCase();
2553     while(elTarget && (elTag != "table")) {
2554         switch(elTag) {
2555             case "body":
2556                 return;
2557             case "li":
2558                 if(oSelf.prehighlightClassName) {
2559                     oSelf._togglePrehighlight(elTarget,"mouseout");
2560                 }
2561                 else {
2562                     oSelf._toggleHighlight(elTarget,"from");
2563                 }
2564             
2565                 oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2566                 break;
2567             case "ul":
2568                 oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2569                 break;
2570             case "div":
2571                 if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2572                     oSelf._bOverContainer = false;
2573                     return;
2574                 }
2575                 break;
2576             default:
2577                 break;
2578         }
2579
2580         elTarget = elTarget.parentNode;
2581         if(elTarget) {
2582             elTag = elTarget.nodeName.toLowerCase();
2583         }
2584     }
2585 };
2586
2587 /**
2588  * Handles container click events.
2589  *
2590  * @method _onContainerClick
2591  * @param v {HTMLEvent} The click event.
2592  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2593  * @private
2594  */
2595 YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
2596     var elTarget = YAHOO.util.Event.getTarget(v);
2597     var elTag = elTarget.nodeName.toLowerCase();
2598     while(elTarget && (elTag != "table")) {
2599         switch(elTag) {
2600             case "body":
2601                 return;
2602             case "li":
2603                 // In case item has not been moused over
2604                 oSelf._toggleHighlight(elTarget,"to");
2605                 oSelf._selectItem(elTarget);
2606                 return;
2607             default:
2608                 break;
2609         }
2610
2611         elTarget = elTarget.parentNode;
2612         if(elTarget) {
2613             elTag = elTarget.nodeName.toLowerCase();
2614         }
2615     }    
2616 };
2617
2618
2619 /**
2620  * Handles container scroll events.
2621  *
2622  * @method _onContainerScroll
2623  * @param v {HTMLEvent} The scroll event.
2624  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2625  * @private
2626  */
2627 YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2628     oSelf._focus();
2629 };
2630
2631 /**
2632  * Handles container resize events.
2633  *
2634  * @method _onContainerResize
2635  * @param v {HTMLEvent} The resize event.
2636  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2637  * @private
2638  */
2639 YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2640     oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2641 };
2642
2643
2644 /**
2645  * Handles textbox keydown events of functional keys, mainly for UI behavior.
2646  *
2647  * @method _onTextboxKeyDown
2648  * @param v {HTMLEvent} The keydown event.
2649  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2650  * @private
2651  */
2652 YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2653     var nKeyCode = v.keyCode;
2654
2655     // Clear timeout
2656     if(oSelf._nTypeAheadDelayID != -1) {
2657         clearTimeout(oSelf._nTypeAheadDelayID);
2658     }
2659     
2660     switch (nKeyCode) {
2661         case 9: // tab
2662             if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2663                 // select an item or clear out
2664                 if(oSelf._elCurListItem) {
2665                     if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
2666                         if(oSelf._bContainerOpen) {
2667                             YAHOO.util.Event.stopEvent(v);
2668                         }
2669                     }
2670                     oSelf._selectItem(oSelf._elCurListItem);
2671                 }
2672                 else {
2673                     oSelf._toggleContainer(false);
2674                 }
2675             }
2676             break;
2677         case 13: // enter
2678             if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2679                 if(oSelf._elCurListItem) {
2680                     if(oSelf._nKeyCode != nKeyCode) {
2681                         if(oSelf._bContainerOpen) {
2682                             YAHOO.util.Event.stopEvent(v);
2683                         }
2684                     }
2685                     oSelf._selectItem(oSelf._elCurListItem);
2686                 }
2687                 else {
2688                     oSelf._toggleContainer(false);
2689                 }
2690             }
2691             break;
2692         case 27: // esc
2693             oSelf._toggleContainer(false);
2694             return;
2695         case 39: // right
2696             oSelf._jumpSelection();
2697             break;
2698         case 38: // up
2699             if(oSelf._bContainerOpen) {
2700                 YAHOO.util.Event.stopEvent(v);
2701                 oSelf._moveSelection(nKeyCode);
2702             }
2703             break;
2704         case 40: // down
2705             if(oSelf._bContainerOpen) {
2706                 YAHOO.util.Event.stopEvent(v);
2707                 oSelf._moveSelection(nKeyCode);
2708             }
2709             break;
2710         default: 
2711             oSelf._bItemSelected = false;
2712             oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2713
2714             oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2715             break;
2716     }
2717
2718     if(nKeyCode === 18){
2719         oSelf._enableIntervalDetection();
2720     }    
2721     oSelf._nKeyCode = nKeyCode;
2722 };
2723
2724 /**
2725  * Handles textbox keypress events.
2726  * @method _onTextboxKeyPress
2727  * @param v {HTMLEvent} The keypress event.
2728  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2729  * @private
2730  */
2731 YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2732     var nKeyCode = v.keyCode;
2733
2734         // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and  Opera browsers (bug 583531),
2735         // where stopEvent is ineffective on keydown events 
2736         if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
2737             switch (nKeyCode) {
2738             case 9: // tab
2739                 // select an item or clear out
2740                 if(oSelf._bContainerOpen) {
2741                     if(oSelf.delimChar) {
2742                         YAHOO.util.Event.stopEvent(v);
2743                     }
2744                     if(oSelf._elCurListItem) {
2745                         oSelf._selectItem(oSelf._elCurListItem);
2746                     }
2747                     else {
2748                         oSelf._toggleContainer(false);
2749                     }
2750                 }
2751                 break;
2752             case 13: // enter
2753                 if(oSelf._bContainerOpen) {
2754                     YAHOO.util.Event.stopEvent(v);
2755                     if(oSelf._elCurListItem) {
2756                         oSelf._selectItem(oSelf._elCurListItem);
2757                     }
2758                     else {
2759                         oSelf._toggleContainer(false);
2760                     }
2761                 }
2762                 break;
2763             default:
2764                 break;
2765             }
2766         }
2767
2768         //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
2769         // Korean IME detected
2770         else if(nKeyCode == 229) {
2771             oSelf._enableIntervalDetection();
2772         }
2773 };
2774
2775 /**
2776  * Handles textbox keyup events to trigger queries.
2777  *
2778  * @method _onTextboxKeyUp
2779  * @param v {HTMLEvent} The keyup event.
2780  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2781  * @private
2782  */
2783 YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2784     var sText = this.value; //string in textbox
2785     
2786     // Check to see if any of the public properties have been updated
2787     oSelf._initProps();
2788
2789     // Filter out chars that don't trigger queries
2790     var nKeyCode = v.keyCode;
2791     if(oSelf._isIgnoreKey(nKeyCode)) {
2792         return;
2793     }
2794
2795     // Clear previous timeout
2796     if(oSelf._nDelayID != -1) {
2797         clearTimeout(oSelf._nDelayID);
2798     }
2799
2800     // Set new timeout
2801     oSelf._nDelayID = setTimeout(function(){
2802             oSelf._sendQuery(sText);
2803         },(oSelf.queryDelay * 1000));
2804 };
2805
2806 /**
2807  * Handles text input box receiving focus.
2808  *
2809  * @method _onTextboxFocus
2810  * @param v {HTMLEvent} The focus event.
2811  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2812  * @private
2813  */
2814 YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2815     // Start of a new interaction
2816     if(!oSelf._bFocused) {
2817         oSelf._elTextbox.setAttribute("autocomplete","off");
2818         oSelf._bFocused = true;
2819         oSelf._sInitInputValue = oSelf._elTextbox.value;
2820         oSelf.textboxFocusEvent.fire(oSelf);
2821     }
2822 };
2823
2824 /**
2825  * Handles text input box losing focus.
2826  *
2827  * @method _onTextboxBlur
2828  * @param v {HTMLEvent} The focus event.
2829  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2830  * @private
2831  */
2832 YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2833     // Is a true blur
2834     if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2835         // Current query needs to be validated as a selection
2836         if(!oSelf._bItemSelected) {
2837             var elMatchListItem = oSelf._textMatchesOption();
2838             // Container is closed or current query doesn't match any result
2839             if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
2840                 // Force selection is enabled so clear the current query
2841                 if(oSelf.forceSelection) {
2842                     oSelf._clearSelection();
2843                 }
2844                 // Treat current query as a valid selection
2845                 else {
2846                     oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2847                 }
2848             }
2849             // Container is open and current query matches a result
2850             else {
2851                 // Force a selection when textbox is blurred with a match
2852                 if(oSelf.forceSelection) {
2853                     oSelf._selectItem(elMatchListItem);
2854                 }
2855             }
2856         }
2857
2858         oSelf._clearInterval();
2859         oSelf._bFocused = false;
2860         if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2861             oSelf.textboxChangeEvent.fire(oSelf);
2862         }
2863         oSelf.textboxBlurEvent.fire(oSelf);
2864
2865         oSelf._toggleContainer(false);
2866     }
2867     // Not a true blur if it was a selection via mouse click
2868     else {
2869         oSelf._focus();
2870     }
2871 };
2872
2873 /**
2874  * Handles window unload event.
2875  *
2876  * @method _onWindowUnload
2877  * @param v {HTMLEvent} The unload event.
2878  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2879  * @private
2880  */
2881 YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2882     if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2883         oSelf._elTextbox.setAttribute("autocomplete","on");
2884     }
2885 };
2886
2887 /////////////////////////////////////////////////////////////////////////////
2888 //
2889 // Deprecated for Backwards Compatibility
2890 //
2891 /////////////////////////////////////////////////////////////////////////////
2892 /**
2893  * @method doBeforeSendQuery
2894  * @deprecated Use generateRequest.
2895  */
2896 YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
2897     return this.generateRequest(sQuery);
2898 };
2899
2900 /**
2901  * @method getListItems
2902  * @deprecated Use getListEl().childNodes.
2903  */
2904 YAHOO.widget.AutoComplete.prototype.getListItems = function() {
2905     var allListItemEls = [],
2906         els = this._elList.childNodes;
2907     for(var i=els.length-1; i>=0; i--) {
2908         allListItemEls[i] = els[i];
2909     }
2910     return allListItemEls;
2911 };
2912
2913 /////////////////////////////////////////////////////////////////////////
2914 //
2915 // Private static methods
2916 //
2917 /////////////////////////////////////////////////////////////////////////
2918
2919 /**
2920  * Clones object literal or array of object literals.
2921  *
2922  * @method AutoComplete._cloneObject
2923  * @param o {Object} Object.
2924  * @private
2925  * @static     
2926  */
2927 YAHOO.widget.AutoComplete._cloneObject = function(o) {
2928     if(!YAHOO.lang.isValue(o)) {
2929         return o;
2930     }
2931     
2932     var copy = {};
2933     
2934     if(YAHOO.lang.isFunction(o)) {
2935         copy = o;
2936     }
2937     else if(YAHOO.lang.isArray(o)) {
2938         var array = [];
2939         for(var i=0,len=o.length;i<len;i++) {
2940             array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
2941         }
2942         copy = array;
2943     }
2944     else if(YAHOO.lang.isObject(o)) { 
2945         for (var x in o){
2946             if(YAHOO.lang.hasOwnProperty(o, x)) {
2947                 if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
2948                     copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
2949                 }
2950                 else {
2951                     copy[x] = o[x];
2952                 }
2953             }
2954         }
2955     }
2956     else {
2957         copy = o;
2958     }
2959
2960     return copy;
2961 };
2962
2963
2964
2965
2966 YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.8.0r4", build: "2449"});