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