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