MDL-11517 reserved word MOD used in table alias in questions backup code
[moodle-pu.git] / lib / yui / autocomplete / autocomplete.js
blob72a6772b599b9f52c483b181a7cc70d40f919b97
1 /*
2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.3.0
6 */
7  /**
8  * The AutoComplete control provides the front-end logic for text-entry suggestion and
9  * completion functionality.
10  *
11  * @module autocomplete
12  * @requires yahoo, dom, event, datasource
13  * @optional animation, connection
14  * @namespace YAHOO.widget
15  * @title AutoComplete Widget
16  */
18 /****************************************************************************/
19 /****************************************************************************/
20 /****************************************************************************/
22 /**
23  * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
24  * auto completion widget.  Some key features:
25  * <ul>
26  * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
27  * <li>The drop down container can "roll down" or "fly out" via configurable
28  * animation</li>
29  * <li>UI look-and-feel customizable through CSS, including container
30  * attributes, borders, position, fonts, etc</li>
31  * </ul>
32  *
33  * @class AutoComplete
34  * @constructor
35  * @param elInput {HTMLElement} DOM element reference of an input field.
36  * @param elInput {String} String ID of an input field.
37  * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
38  * @param elContainer {String} String ID of an existing DIV.
39  * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
40  * @param oConfigs {Object} (optional) Object literal of configuration params.
41  */
42 YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
43     if(elInput && elContainer && oDataSource) {
44         // Validate DataSource
45         if(oDataSource instanceof YAHOO.widget.DataSource) {
46             this.dataSource = oDataSource;
47         }
48         else {
49             return;
50         }
52         // Validate input element
53         if(YAHOO.util.Dom.inDocument(elInput)) {
54             if(YAHOO.lang.isString(elInput)) {
55                     this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
56                     this._oTextbox = document.getElementById(elInput);
57             }
58             else {
59                 this._sName = (elInput.id) ?
60                     "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
61                     "instance" + YAHOO.widget.AutoComplete._nIndex;
62                 this._oTextbox = elInput;
63             }
64             YAHOO.util.Dom.addClass(this._oTextbox, "yui-ac-input");
65         }
66         else {
67             return;
68         }
70         // Validate container element
71         if(YAHOO.util.Dom.inDocument(elContainer)) {
72             if(YAHOO.lang.isString(elContainer)) {
73                     this._oContainer = document.getElementById(elContainer);
74             }
75             else {
76                 this._oContainer = elContainer;
77             }
78             if(this._oContainer.style.display == "none") {
79             }
80             
81             // For skinning
82             var elParent = this._oContainer.parentNode;
83             var elTag = elParent.tagName.toLowerCase();
84             while(elParent && (elParent != "document")) {
85                 if(elTag == "div") {
86                     YAHOO.util.Dom.addClass(elParent, "yui-ac");
87                     break;
88                 }
89                 else {
90                     elParent = elParent.parentNode;
91                     elTag = elParent.tagName.toLowerCase();
92                 }
93             }
94             if(elTag != "div") {
95             }
96         }
97         else {
98             return;
99         }
101         // Set any config params passed in to override defaults
102         if(oConfigs && (oConfigs.constructor == Object)) {
103             for(var sConfig in oConfigs) {
104                 if(sConfig) {
105                     this[sConfig] = oConfigs[sConfig];
106                 }
107             }
108         }
110         // Initialization sequence
111         this._initContainer();
112         this._initProps();
113         this._initList();
114         this._initContainerHelpers();
116         // Set up events
117         var oSelf = this;
118         var oTextbox = this._oTextbox;
119         // Events are actually for the content module within the container
120         var oContent = this._oContainer._oContent;
122         // Dom events
123         YAHOO.util.Event.addListener(oTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
124         YAHOO.util.Event.addListener(oTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
125         YAHOO.util.Event.addListener(oTextbox,"focus",oSelf._onTextboxFocus,oSelf);
126         YAHOO.util.Event.addListener(oTextbox,"blur",oSelf._onTextboxBlur,oSelf);
127         YAHOO.util.Event.addListener(oContent,"mouseover",oSelf._onContainerMouseover,oSelf);
128         YAHOO.util.Event.addListener(oContent,"mouseout",oSelf._onContainerMouseout,oSelf);
129         YAHOO.util.Event.addListener(oContent,"scroll",oSelf._onContainerScroll,oSelf);
130         YAHOO.util.Event.addListener(oContent,"resize",oSelf._onContainerResize,oSelf);
131         if(oTextbox.form) {
132             YAHOO.util.Event.addListener(oTextbox.form,"submit",oSelf._onFormSubmit,oSelf);
133         }
134         YAHOO.util.Event.addListener(oTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
136         // Custom events
137         this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
138         this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
139         this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
140         this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
141         this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
142         this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
143         this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
144         this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
145         this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
146         this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
147         this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
148         this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
149         this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
150         this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
151         this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
152         this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
153         
154         // Finish up
155         oTextbox.setAttribute("autocomplete","off");
156         YAHOO.widget.AutoComplete._nIndex++;
157     }
158     // Required arguments were not found
159     else {
160     }
163 /////////////////////////////////////////////////////////////////////////////
165 // Public member variables
167 /////////////////////////////////////////////////////////////////////////////
170  * The DataSource object that encapsulates the data used for auto completion.
171  * This object should be an inherited object from YAHOO.widget.DataSource.
173  * @property dataSource
174  * @type YAHOO.widget.DataSource
175  */
176 YAHOO.widget.AutoComplete.prototype.dataSource = null;
179  * Number of characters that must be entered before querying for results. A negative value
180  * effectively turns off the widget. A value of 0 allows queries of null or empty string
181  * values.
183  * @property minQueryLength
184  * @type Number
185  * @default 1
186  */
187 YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
190  * Maximum number of results to display in results container.
192  * @property maxResultsDisplayed
193  * @type Number
194  * @default 10
195  */
196 YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
199  * Number of seconds to delay before submitting a query request.  If a query
200  * request is received before a previous one has completed its delay, the
201  * previous request is cancelled and the new request is set to the delay.
202  * Implementers should take care when setting this value very low (i.e., less
203  * than 0.2) with low latency DataSources and the typeAhead feature enabled, as
204  * fast typers may see unexpected behavior.
206  * @property queryDelay
207  * @type Number
208  * @default 0.2
209  */
210 YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
213  * Class name of a highlighted item within results container.
215  * @property highlightClassName
216  * @type String
217  * @default "yui-ac-highlight"
218  */
219 YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
222  * Class name of a pre-highlighted item within results container.
224  * @property prehighlightClassName
225  * @type String
226  */
227 YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
230  * Query delimiter. A single character separator for multiple delimited
231  * selections. Multiple delimiter characteres may be defined as an array of
232  * strings. A null value or empty string indicates that query results cannot
233  * be delimited. This feature is not recommended if you need forceSelection to
234  * be true.
236  * @property delimChar
237  * @type String | String[]
238  */
239 YAHOO.widget.AutoComplete.prototype.delimChar = null;
242  * Whether or not the first item in results container should be automatically highlighted
243  * on expand.
245  * @property autoHighlight
246  * @type Boolean
247  * @default true
248  */
249 YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
252  * Whether or not the input field should be automatically updated
253  * with the first query result as the user types, auto-selecting the substring
254  * that the user has not typed.
256  * @property typeAhead
257  * @type Boolean
258  * @default false
259  */
260 YAHOO.widget.AutoComplete.prototype.typeAhead = false;
263  * Whether or not to animate the expansion/collapse of the results container in the
264  * horizontal direction.
266  * @property animHoriz
267  * @type Boolean
268  * @default false
269  */
270 YAHOO.widget.AutoComplete.prototype.animHoriz = false;
273  * Whether or not to animate the expansion/collapse of the results container in the
274  * vertical direction.
276  * @property animVert
277  * @type Boolean
278  * @default true
279  */
280 YAHOO.widget.AutoComplete.prototype.animVert = true;
283  * Speed of container expand/collapse animation, in seconds..
285  * @property animSpeed
286  * @type Number
287  * @default 0.3
288  */
289 YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
292  * Whether or not to force the user's selection to match one of the query
293  * results. Enabling this feature essentially transforms the input field into a
294  * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
295  * defined.
297  * @property forceSelection
298  * @type Boolean
299  * @default false
300  */
301 YAHOO.widget.AutoComplete.prototype.forceSelection = false;
304  * Whether or not to allow browsers to cache user-typed input in the input
305  * field. Disabling this feature will prevent the widget from setting the
306  * autocomplete="off" on the input field. When autocomplete="off"
307  * and users click the back button after form submission, user-typed input can
308  * be prefilled by the browser from its cache. This caching of user input may
309  * not be desired for sensitive data, such as credit card numbers, in which
310  * case, implementers should consider setting allowBrowserAutocomplete to false.
312  * @property allowBrowserAutocomplete
313  * @type Boolean
314  * @default true
315  */
316 YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
319  * Whether or not the results container should always be displayed.
320  * Enabling this feature displays the container when the widget is instantiated
321  * and prevents the toggling of the container to a collapsed state.
323  * @property alwaysShowContainer
324  * @type Boolean
325  * @default false
326  */
327 YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
330  * Whether or not to use an iFrame to layer over Windows form elements in
331  * IE. Set to true only when the results container will be on top of a
332  * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
333  * 5.5 < IE < 7).
335  * @property useIFrame
336  * @type Boolean
337  * @default false
338  */
339 YAHOO.widget.AutoComplete.prototype.useIFrame = false;
342  * Whether or not the results container should have a shadow.
344  * @property useShadow
345  * @type Boolean
346  * @default false
347  */
348 YAHOO.widget.AutoComplete.prototype.useShadow = false;
350 /////////////////////////////////////////////////////////////////////////////
352 // Public methods
354 /////////////////////////////////////////////////////////////////////////////
356  /**
357  * Public accessor to the unique name of the AutoComplete instance.
359  * @method toString
360  * @return {String} Unique name of the AutoComplete instance.
361  */
362 YAHOO.widget.AutoComplete.prototype.toString = function() {
363     return "AutoComplete " + this._sName;
366  /**
367  * Returns true if container is in an expanded state, false otherwise.
369  * @method isContainerOpen
370  * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
371  */
372 YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
373     return this._bContainerOpen;
377  * Public accessor to the internal array of DOM &lt;li&gt; elements that
378  * display query results within the results container.
380  * @method getListItems
381  * @return {HTMLElement[]} Array of &lt;li&gt; elements within the results container.
382  */
383 YAHOO.widget.AutoComplete.prototype.getListItems = function() {
384     return this._aListItems;
388  * Public accessor to the data held in an &lt;li&gt; element of the
389  * results container.
391  * @method getListItemData
392  * @return {Object | Object[]} Object or array of result data or null
393  */
394 YAHOO.widget.AutoComplete.prototype.getListItemData = function(oListItem) {
395     if(oListItem._oResultData) {
396         return oListItem._oResultData;
397     }
398     else {
399         return false;
400     }
404  * Sets HTML markup for the results container header. This markup will be
405  * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
407  * @method setHeader
408  * @param sHeader {String} HTML markup for results container header.
409  */
410 YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
411     if(sHeader) {
412         if(this._oContainer._oContent._oHeader) {
413             this._oContainer._oContent._oHeader.innerHTML = sHeader;
414             this._oContainer._oContent._oHeader.style.display = "block";
415         }
416     }
417     else {
418         this._oContainer._oContent._oHeader.innerHTML = "";
419         this._oContainer._oContent._oHeader.style.display = "none";
420     }
424  * Sets HTML markup for the results container footer. This markup will be
425  * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
427  * @method setFooter
428  * @param sFooter {String} HTML markup for results container footer.
429  */
430 YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
431     if(sFooter) {
432         if(this._oContainer._oContent._oFooter) {
433             this._oContainer._oContent._oFooter.innerHTML = sFooter;
434             this._oContainer._oContent._oFooter.style.display = "block";
435         }
436     }
437     else {
438         this._oContainer._oContent._oFooter.innerHTML = "";
439         this._oContainer._oContent._oFooter.style.display = "none";
440     }
444  * Sets HTML markup for the results container body. This markup will be
445  * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
447  * @method setBody
448  * @param sBody {String} HTML markup for results container body.
449  */
450 YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
451     if(sBody) {
452         if(this._oContainer._oContent._oBody) {
453             this._oContainer._oContent._oBody.innerHTML = sBody;
454             this._oContainer._oContent._oBody.style.display = "block";
455             this._oContainer._oContent.style.display = "block";
456         }
457     }
458     else {
459         this._oContainer._oContent._oBody.innerHTML = "";
460         this._oContainer._oContent.style.display = "none";
461     }
462     this._maxResultsDisplayed = 0;
466  * Overridable method that converts a result item object into HTML markup
467  * for display. Return data values are accessible via the oResultItem object,
468  * and the key return value will always be oResultItem[0]. Markup will be
469  * displayed within &lt;li&gt; element tags in the container.
471  * @method formatResult
472  * @param oResultItem {Object} Result item representing one query result. Data is held in an array.
473  * @param sQuery {String} The current query string.
474  * @return {String} HTML markup of formatted result data.
475  */
476 YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultItem, sQuery) {
477     var sResult = oResultItem[0];
478     if(sResult) {
479         return sResult;
480     }
481     else {
482         return "";
483     }
487  * Overridable method called before container expands allows implementers to access data
488  * and DOM elements.
490  * @method doBeforeExpandContainer
491  * @param oTextbox {HTMLElement} The text input box.
492  * @param oContainer {HTMLElement} The container element.
493  * @param sQuery {String} The query string.
494  * @param aResults {Object[]}  An array of query results.
495  * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
496  */
497 YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(oTextbox, oContainer, sQuery, aResults) {
498     return true;
502  * Makes query request to the DataSource.
504  * @method sendQuery
505  * @param sQuery {String} Query string.
506  */
507 YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
508     this._sendQuery(sQuery);
512  * Overridable method gives implementers access to the query before it gets sent.
514  * @method doBeforeSendQuery
515  * @param sQuery {String} Query string.
516  * @return {String} Query string.
517  */
518 YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
519     return sQuery;
523  * Nulls out the entire AutoComplete instance and related objects, removes attached
524  * event listeners, and clears out DOM elements inside the container. After
525  * calling this method, the instance reference should be expliclitly nulled by
526  * implementer, as in myDataTable = null. Use with caution!
528  * @method destroy
529  */
530 YAHOO.widget.AutoComplete.prototype.destroy = function() {
531     var instanceName = this.toString();
532     var elInput = this._oTextbox;
533     var elContainer = this._oContainer;
535     // Unhook custom events
536     this.textboxFocusEvent.unsubscribe();
537     this.textboxKeyEvent.unsubscribe();
538     this.dataRequestEvent.unsubscribe();
539     this.dataReturnEvent.unsubscribe();
540     this.dataErrorEvent.unsubscribe();
541     this.containerExpandEvent.unsubscribe();
542     this.typeAheadEvent.unsubscribe();
543     this.itemMouseOverEvent.unsubscribe();
544     this.itemMouseOutEvent.unsubscribe();
545     this.itemArrowToEvent.unsubscribe();
546     this.itemArrowFromEvent.unsubscribe();
547     this.itemSelectEvent.unsubscribe();
548     this.unmatchedItemSelectEvent.unsubscribe();
549     this.selectionEnforceEvent.unsubscribe();
550     this.containerCollapseEvent.unsubscribe();
551     this.textboxBlurEvent.unsubscribe();
553     // Unhook DOM events
554     YAHOO.util.Event.purgeElement(elInput, true);
555     YAHOO.util.Event.purgeElement(elContainer, true);
557     // Remove DOM elements
558     elContainer.innerHTML = "";
560     // Null out objects
561     for(var key in this) {
562         if(this.hasOwnProperty(key)) {
563             this[key] = null;
564         }
565     }
569 /////////////////////////////////////////////////////////////////////////////
571 // Public events
573 /////////////////////////////////////////////////////////////////////////////
576  * Fired when the input field receives focus.
578  * @event textboxFocusEvent
579  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
580  */
581 YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
584  * Fired when the input field receives key input.
586  * @event textboxKeyEvent
587  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
588  * @param nKeycode {Number} The keycode number.
589  */
590 YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
593  * Fired when the AutoComplete instance makes a query to the DataSource.
594  * 
595  * @event dataRequestEvent
596  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
597  * @param sQuery {String} The query string.
598  */
599 YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
602  * Fired when the AutoComplete instance receives query results from the data
603  * source.
605  * @event dataReturnEvent
606  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
607  * @param sQuery {String} The query string.
608  * @param aResults {Object[]} Results array.
609  */
610 YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
613  * Fired when the AutoComplete instance does not receive query results from the
614  * DataSource due to an error.
616  * @event dataErrorEvent
617  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
618  * @param sQuery {String} The query string.
619  */
620 YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
623  * Fired when the results container is expanded.
625  * @event containerExpandEvent
626  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
627  */
628 YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
631  * Fired when the input field has been prefilled by the type-ahead
632  * feature. 
634  * @event typeAheadEvent
635  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
636  * @param sQuery {String} The query string.
637  * @param sPrefill {String} The prefill string.
638  */
639 YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
642  * Fired when result item has been moused over.
644  * @event itemMouseOverEvent
645  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
646  * @param elItem {HTMLElement} The &lt;li&gt element item moused to.
647  */
648 YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
651  * Fired when result item has been moused out.
653  * @event itemMouseOutEvent
654  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
655  * @param elItem {HTMLElement} The &lt;li&gt; element item moused from.
656  */
657 YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
660  * Fired when result item has been arrowed to. 
662  * @event itemArrowToEvent
663  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
664  * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed to.
665  */
666 YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
669  * Fired when result item has been arrowed away from.
671  * @event itemArrowFromEvent
672  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
673  * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed from.
674  */
675 YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
678  * Fired when an item is selected via mouse click, ENTER key, or TAB key.
680  * @event itemSelectEvent
681  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
682  * @param elItem {HTMLElement} The selected &lt;li&gt; element item.
683  * @param oData {Object} The data returned for the item, either as an object,
684  * or mapped from the schema into an array.
685  */
686 YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
689  * Fired when a user selection does not match any of the displayed result items.
690  * Note that this event may not behave as expected when delimiter characters
691  * have been defined. 
693  * @event unmatchedItemSelectEvent
694  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
695  * @param sQuery {String} The user-typed query string.
696  */
697 YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
700  * Fired if forceSelection is enabled and the user's input has been cleared
701  * because it did not match one of the returned query results.
703  * @event selectionEnforceEvent
704  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
705  */
706 YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
709  * Fired when the results container is collapsed.
711  * @event containerCollapseEvent
712  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
713  */
714 YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
717  * Fired when the input field loses focus.
719  * @event textboxBlurEvent
720  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
721  */
722 YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
724 /////////////////////////////////////////////////////////////////////////////
726 // Private member variables
728 /////////////////////////////////////////////////////////////////////////////
731  * Internal class variable to index multiple AutoComplete instances.
733  * @property _nIndex
734  * @type Number
735  * @default 0
736  * @private
737  */
738 YAHOO.widget.AutoComplete._nIndex = 0;
741  * Name of AutoComplete instance.
743  * @property _sName
744  * @type String
745  * @private
746  */
747 YAHOO.widget.AutoComplete.prototype._sName = null;
750  * Text input field DOM element.
752  * @property _oTextbox
753  * @type HTMLElement
754  * @private
755  */
756 YAHOO.widget.AutoComplete.prototype._oTextbox = null;
759  * Whether or not the input field is currently in focus. If query results come back
760  * but the user has already moved on, do not proceed with auto complete behavior.
762  * @property _bFocused
763  * @type Boolean
764  * @private
765  */
766 YAHOO.widget.AutoComplete.prototype._bFocused = true;
769  * Animation instance for container expand/collapse.
771  * @property _oAnim
772  * @type Boolean
773  * @private
774  */
775 YAHOO.widget.AutoComplete.prototype._oAnim = null;
778  * Container DOM element.
780  * @property _oContainer
781  * @type HTMLElement
782  * @private
783  */
784 YAHOO.widget.AutoComplete.prototype._oContainer = null;
787  * Whether or not the results container is currently open.
789  * @property _bContainerOpen
790  * @type Boolean
791  * @private
792  */
793 YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
796  * Whether or not the mouse is currently over the results
797  * container. This is necessary in order to prevent clicks on container items
798  * from being text input field blur events.
800  * @property _bOverContainer
801  * @type Boolean
802  * @private
803  */
804 YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
807  * Array of &lt;li&gt; elements references that contain query results within the
808  * results container.
810  * @property _aListItems
811  * @type HTMLElement[]
812  * @private
813  */
814 YAHOO.widget.AutoComplete.prototype._aListItems = null;
817  * Number of &lt;li&gt; elements currently displayed in results container.
819  * @property _nDisplayedItems
820  * @type Number
821  * @private
822  */
823 YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
826  * Internal count of &lt;li&gt; elements displayed and hidden in results container.
828  * @property _maxResultsDisplayed
829  * @type Number
830  * @private
831  */
832 YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
835  * Current query string
837  * @property _sCurQuery
838  * @type String
839  * @private
840  */
841 YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
844  * Past queries this session (for saving delimited queries).
846  * @property _sSavedQuery
847  * @type String
848  * @private
849  */
850 YAHOO.widget.AutoComplete.prototype._sSavedQuery = null;
853  * Pointer to the currently highlighted &lt;li&gt; element in the container.
855  * @property _oCurItem
856  * @type HTMLElement
857  * @private
858  */
859 YAHOO.widget.AutoComplete.prototype._oCurItem = null;
862  * Whether or not an item has been selected since the container was populated
863  * with results. Reset to false by _populateList, and set to true when item is
864  * selected.
866  * @property _bItemSelected
867  * @type Boolean
868  * @private
869  */
870 YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
873  * Key code of the last key pressed in textbox.
875  * @property _nKeyCode
876  * @type Number
877  * @private
878  */
879 YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
882  * Delay timeout ID.
884  * @property _nDelayID
885  * @type Number
886  * @private
887  */
888 YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
891  * Src to iFrame used when useIFrame = true. Supports implementations over SSL
892  * as well.
894  * @property _iFrameSrc
895  * @type String
896  * @private
897  */
898 YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
901  * For users typing via certain IMEs, queries must be triggered by intervals,
902  * since key events yet supported across all browsers for all IMEs.
904  * @property _queryInterval
905  * @type Object
906  * @private
907  */
908 YAHOO.widget.AutoComplete.prototype._queryInterval = null;
911  * Internal tracker to last known textbox value, used to determine whether or not
912  * to trigger a query via interval for certain IME users.
914  * @event _sLastTextboxValue
915  * @type String
916  * @private
917  */
918 YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
920 /////////////////////////////////////////////////////////////////////////////
922 // Private methods
924 /////////////////////////////////////////////////////////////////////////////
927  * Updates and validates latest public config properties.
929  * @method __initProps
930  * @private
931  */
932 YAHOO.widget.AutoComplete.prototype._initProps = function() {
933     // Correct any invalid values
934     var minQueryLength = this.minQueryLength;
935     if(!YAHOO.lang.isNumber(minQueryLength)) {
936         this.minQueryLength = 1;
937     }
938     var maxResultsDisplayed = this.maxResultsDisplayed;
939     if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
940         this.maxResultsDisplayed = 10;
941     }
942     var queryDelay = this.queryDelay;
943     if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
944         this.queryDelay = 0.2;
945     }
946     var delimChar = this.delimChar;
947     if(YAHOO.lang.isString(delimChar)) {
948         this.delimChar = [delimChar];
949     }
950     else if(!YAHOO.lang.isArray(delimChar)) {
951         this.delimChar = null;
952     }
953     var animSpeed = this.animSpeed;
954     if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
955         if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
956             this.animSpeed = 0.3;
957         }
958         if(!this._oAnim ) {
959             this._oAnim = new YAHOO.util.Anim(this._oContainer._oContent, {}, this.animSpeed);
960         }
961         else {
962             this._oAnim.duration = this.animSpeed;
963         }
964     }
965     if(this.forceSelection && delimChar) {
966     }
970  * Initializes the results container helpers if they are enabled and do
971  * not exist
973  * @method _initContainerHelpers
974  * @private
975  */
976 YAHOO.widget.AutoComplete.prototype._initContainerHelpers = function() {
977     if(this.useShadow && !this._oContainer._oShadow) {
978         var oShadow = document.createElement("div");
979         oShadow.className = "yui-ac-shadow";
980         this._oContainer._oShadow = this._oContainer.appendChild(oShadow);
981     }
982     if(this.useIFrame && !this._oContainer._oIFrame) {
983         var oIFrame = document.createElement("iframe");
984         oIFrame.src = this._iFrameSrc;
985         oIFrame.frameBorder = 0;
986         oIFrame.scrolling = "no";
987         oIFrame.style.position = "absolute";
988         oIFrame.style.width = "100%";
989         oIFrame.style.height = "100%";
990         oIFrame.tabIndex = -1;
991         this._oContainer._oIFrame = this._oContainer.appendChild(oIFrame);
992     }
996  * Initializes the results container once at object creation
998  * @method _initContainer
999  * @private
1000  */
1001 YAHOO.widget.AutoComplete.prototype._initContainer = function() {
1002     YAHOO.util.Dom.addClass(this._oContainer, "yui-ac-container");
1003     
1004     if(!this._oContainer._oContent) {
1005         // The oContent div helps size the iframe and shadow properly
1006         var oContent = document.createElement("div");
1007         oContent.className = "yui-ac-content";
1008         oContent.style.display = "none";
1009         this._oContainer._oContent = this._oContainer.appendChild(oContent);
1011         var oHeader = document.createElement("div");
1012         oHeader.className = "yui-ac-hd";
1013         oHeader.style.display = "none";
1014         this._oContainer._oContent._oHeader = this._oContainer._oContent.appendChild(oHeader);
1016         var oBody = document.createElement("div");
1017         oBody.className = "yui-ac-bd";
1018         this._oContainer._oContent._oBody = this._oContainer._oContent.appendChild(oBody);
1020         var oFooter = document.createElement("div");
1021         oFooter.className = "yui-ac-ft";
1022         oFooter.style.display = "none";
1023         this._oContainer._oContent._oFooter = this._oContainer._oContent.appendChild(oFooter);
1024     }
1025     else {
1026     }
1030  * Clears out contents of container body and creates up to
1031  * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1032  * &lt;ul&gt; element.
1034  * @method _initList
1035  * @private
1036  */
1037 YAHOO.widget.AutoComplete.prototype._initList = function() {
1038     this._aListItems = [];
1039     while(this._oContainer._oContent._oBody.hasChildNodes()) {
1040         var oldListItems = this.getListItems();
1041         if(oldListItems) {
1042             for(var oldi = oldListItems.length-1; oldi >= 0; oldi--) {
1043                 oldListItems[oldi] = null;
1044             }
1045         }
1046         this._oContainer._oContent._oBody.innerHTML = "";
1047     }
1049     var oList = document.createElement("ul");
1050     oList = this._oContainer._oContent._oBody.appendChild(oList);
1051     for(var i=0; i<this.maxResultsDisplayed; i++) {
1052         var oItem = document.createElement("li");
1053         oItem = oList.appendChild(oItem);
1054         this._aListItems[i] = oItem;
1055         this._initListItem(oItem, i);
1056     }
1057     this._maxResultsDisplayed = this.maxResultsDisplayed;
1061  * Initializes each &lt;li&gt; element in the container list.
1063  * @method _initListItem
1064  * @param oItem {HTMLElement} The &lt;li&gt; DOM element.
1065  * @param nItemIndex {Number} The index of the element.
1066  * @private
1067  */
1068 YAHOO.widget.AutoComplete.prototype._initListItem = function(oItem, nItemIndex) {
1069     var oSelf = this;
1070     oItem.style.display = "none";
1071     oItem._nItemIndex = nItemIndex;
1073     oItem.mouseover = oItem.mouseout = oItem.onclick = null;
1074     YAHOO.util.Event.addListener(oItem,"mouseover",oSelf._onItemMouseover,oSelf);
1075     YAHOO.util.Event.addListener(oItem,"mouseout",oSelf._onItemMouseout,oSelf);
1076     YAHOO.util.Event.addListener(oItem,"click",oSelf._onItemMouseclick,oSelf);
1080  * Enables interval detection for  Korean IME support.
1082  * @method _onIMEDetected
1083  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1084  * @private
1085  */
1086 YAHOO.widget.AutoComplete.prototype._onIMEDetected = function(oSelf) {
1087     oSelf._enableIntervalDetection();
1091  * Enables query triggers based on text input detection by intervals (rather
1092  * than by key events).
1094  * @method _enableIntervalDetection
1095  * @private
1096  */
1097 YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1098     var currValue = this._oTextbox.value;
1099     var lastValue = this._sLastTextboxValue;
1100     if(currValue != lastValue) {
1101         this._sLastTextboxValue = currValue;
1102         this._sendQuery(currValue);
1103     }
1108  * Cancels text input detection by intervals.
1110  * @method _cancelIntervalDetection
1111  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1112  * @private
1113  */
1114 YAHOO.widget.AutoComplete.prototype._cancelIntervalDetection = function(oSelf) {
1115     if(oSelf._queryInterval) {
1116         clearInterval(oSelf._queryInterval);
1117     }
1122  * Whether or not key is functional or should be ignored. Note that the right
1123  * arrow key is NOT an ignored key since it triggers queries for certain intl
1124  * charsets.
1126  * @method _isIgnoreKey
1127  * @param nKeycode {Number} Code of key pressed.
1128  * @return {Boolean} True if key should be ignored, false otherwise.
1129  * @private
1130  */
1131 YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1132     if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
1133             (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1134             (nKeyCode >= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock
1135             (nKeyCode == 27) || // esc
1136             (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1137             /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1138             (nKeyCode == 40) || // down*/
1139             (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1140             (nKeyCode >= 44 && nKeyCode <= 45)) { // print screen,insert
1141         return true;
1142     }
1143     return false;
1147  * Makes query request to the DataSource.
1149  * @method _sendQuery
1150  * @param sQuery {String} Query string.
1151  * @private
1152  */
1153 YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1154     // Widget has been effectively turned off
1155     if(this.minQueryLength == -1) {
1156         this._toggleContainer(false);
1157         return;
1158     }
1159     // Delimiter has been enabled
1160     var aDelimChar = (this.delimChar) ? this.delimChar : null;
1161     if(aDelimChar) {
1162         // Loop through all possible delimiters and find the latest one
1163         // A " " may be a false positive if they are defined as delimiters AND
1164         // are used to separate delimited queries
1165         var nDelimIndex = -1;
1166         for(var i = aDelimChar.length-1; i >= 0; i--) {
1167             var nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
1168             if(nNewIndex > nDelimIndex) {
1169                 nDelimIndex = nNewIndex;
1170             }
1171         }
1172         // If we think the last delimiter is a space (" "), make sure it is NOT
1173         // a false positive by also checking the char directly before it
1174         if(aDelimChar[i] == " ") {
1175             for (var j = aDelimChar.length-1; j >= 0; j--) {
1176                 if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
1177                     nDelimIndex--;
1178                     break;
1179                 }
1180             }
1181         }
1182         // A delimiter has been found so extract the latest query
1183         if(nDelimIndex > -1) {
1184             var nQueryStart = nDelimIndex + 1;
1185             // Trim any white space from the beginning...
1186             while(sQuery.charAt(nQueryStart) == " ") {
1187                 nQueryStart += 1;
1188             }
1189             // ...and save the rest of the string for later
1190             this._sSavedQuery = sQuery.substring(0,nQueryStart);
1191             // Here is the query itself
1192             sQuery = sQuery.substr(nQueryStart);
1193         }
1194         else if(sQuery.indexOf(this._sSavedQuery) < 0){
1195             this._sSavedQuery = null;
1196         }
1197     }
1199     // Don't search queries that are too short
1200     if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1201         if(this._nDelayID != -1) {
1202             clearTimeout(this._nDelayID);
1203         }
1204         this._toggleContainer(false);
1205         return;
1206     }
1208     sQuery = encodeURIComponent(sQuery);
1209     this._nDelayID = -1;    // Reset timeout ID because request has been made
1210     sQuery = this.doBeforeSendQuery(sQuery);
1211     this.dataRequestEvent.fire(this, sQuery);
1212     this.dataSource.getResults(this._populateList, sQuery, this);
1216  * Populates the array of &lt;li&gt; elements in the container with query
1217  * results. This method is passed to YAHOO.widget.DataSource#getResults as a
1218  * callback function so results from the DataSource instance are returned to the
1219  * AutoComplete instance.
1221  * @method _populateList
1222  * @param sQuery {String} The query string.
1223  * @param aResults {Object[]} An array of query result objects from the DataSource.
1224  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1225  * @private
1226  */
1227 YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, aResults, oSelf) {
1228     if(aResults === null) {
1229         oSelf.dataErrorEvent.fire(oSelf, sQuery);
1230     }
1231     if(!oSelf._bFocused || !aResults) {
1232         return;
1233     }
1235     var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
1236     var contentStyle = oSelf._oContainer._oContent.style;
1237     contentStyle.width = (!isOpera) ? null : "";
1238     contentStyle.height = (!isOpera) ? null : "";
1240     var sCurQuery = decodeURIComponent(sQuery);
1241     oSelf._sCurQuery = sCurQuery;
1242     oSelf._bItemSelected = false;
1244     if(oSelf._maxResultsDisplayed != oSelf.maxResultsDisplayed) {
1245         oSelf._initList();
1246     }
1248     var nItems = Math.min(aResults.length,oSelf.maxResultsDisplayed);
1249     oSelf._nDisplayedItems = nItems;
1250     if(nItems > 0) {
1251         oSelf._initContainerHelpers();
1252         var aItems = oSelf._aListItems;
1254         // Fill items with data
1255         for(var i = nItems-1; i >= 0; i--) {
1256             var oItemi = aItems[i];
1257             var oResultItemi = aResults[i];
1258             oItemi.innerHTML = oSelf.formatResult(oResultItemi, sCurQuery);
1259             oItemi.style.display = "list-item";
1260             oItemi._sResultKey = oResultItemi[0];
1261             oItemi._oResultData = oResultItemi;
1263         }
1265         // Empty out remaining items if any
1266         for(var j = aItems.length-1; j >= nItems ; j--) {
1267             var oItemj = aItems[j];
1268             oItemj.innerHTML = null;
1269             oItemj.style.display = "none";
1270             oItemj._sResultKey = null;
1271             oItemj._oResultData = null;
1272         }
1274         // Expand the container
1275         var ok = oSelf.doBeforeExpandContainer(oSelf._oTextbox, oSelf._oContainer, sQuery, aResults);
1276         oSelf._toggleContainer(ok);
1277         
1278         if(oSelf.autoHighlight) {
1279             // Go to the first item
1280             var oFirstItem = aItems[0];
1281             oSelf._toggleHighlight(oFirstItem,"to");
1282             oSelf.itemArrowToEvent.fire(oSelf, oFirstItem);
1283             oSelf._typeAhead(oFirstItem,sQuery);
1284         }
1285         else {
1286             oSelf._oCurItem = null;
1287         }
1288     }
1289     else {
1290         oSelf._toggleContainer(false);
1291     }
1292     oSelf.dataReturnEvent.fire(oSelf, sQuery, aResults);
1293     
1297  * When forceSelection is true and the user attempts
1298  * leave the text input box without selecting an item from the query results,
1299  * the user selection is cleared.
1301  * @method _clearSelection
1302  * @private
1303  */
1304 YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
1305     var sValue = this._oTextbox.value;
1306     var sChar = (this.delimChar) ? this.delimChar[0] : null;
1307     var nIndex = (sChar) ? sValue.lastIndexOf(sChar, sValue.length-2) : -1;
1308     if(nIndex > -1) {
1309         this._oTextbox.value = sValue.substring(0,nIndex);
1310     }
1311     else {
1312          this._oTextbox.value = "";
1313     }
1314     this._sSavedQuery = this._oTextbox.value;
1316     // Fire custom event
1317     this.selectionEnforceEvent.fire(this);
1321  * Whether or not user-typed value in the text input box matches any of the
1322  * query results.
1324  * @method _textMatchesOption
1325  * @return {HTMLElement} Matching list item element if user-input text matches
1326  * a result, null otherwise.
1327  * @private
1328  */
1329 YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
1330     var foundMatch = null;
1332     for(var i = this._nDisplayedItems-1; i >= 0 ; i--) {
1333         var oItem = this._aListItems[i];
1334         var sMatch = oItem._sResultKey.toLowerCase();
1335         if(sMatch == this._sCurQuery.toLowerCase()) {
1336             foundMatch = oItem;
1337             break;
1338         }
1339     }
1340     return(foundMatch);
1344  * Updates in the text input box with the first query result as the user types,
1345  * selecting the substring that the user has not typed.
1347  * @method _typeAhead
1348  * @param oItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
1349  * @param sQuery {String} Query string.
1350  * @private
1351  */
1352 YAHOO.widget.AutoComplete.prototype._typeAhead = function(oItem, sQuery) {
1353     // Don't update if turned off
1354     if(!this.typeAhead || (this._nKeyCode == 8)) {
1355         return;
1356     }
1358     var oTextbox = this._oTextbox;
1359     var sValue = this._oTextbox.value; // any saved queries plus what user has typed
1361     // Don't update with type-ahead if text selection is not supported
1362     if(!oTextbox.setSelectionRange && !oTextbox.createTextRange) {
1363         return;
1364     }
1366     // Select the portion of text that the user has not typed
1367     var nStart = sValue.length;
1368     this._updateValue(oItem);
1369     var nEnd = oTextbox.value.length;
1370     this._selectText(oTextbox,nStart,nEnd);
1371     var sPrefill = oTextbox.value.substr(nStart,nEnd);
1372     this.typeAheadEvent.fire(this,sQuery,sPrefill);
1376  * Selects text in the input field.
1378  * @method _selectText
1379  * @param oTextbox {HTMLElement} Text input box element in which to select text.
1380  * @param nStart {Number} Starting index of text string to select.
1381  * @param nEnd {Number} Ending index of text selection.
1382  * @private
1383  */
1384 YAHOO.widget.AutoComplete.prototype._selectText = function(oTextbox, nStart, nEnd) {
1385     if(oTextbox.setSelectionRange) { // For Mozilla
1386         oTextbox.setSelectionRange(nStart,nEnd);
1387     }
1388     else if(oTextbox.createTextRange) { // For IE
1389         var oTextRange = oTextbox.createTextRange();
1390         oTextRange.moveStart("character", nStart);
1391         oTextRange.moveEnd("character", nEnd-oTextbox.value.length);
1392         oTextRange.select();
1393     }
1394     else {
1395         oTextbox.select();
1396     }
1400  * Syncs results container with its helpers.
1402  * @method _toggleContainerHelpers
1403  * @param bShow {Boolean} True if container is expanded, false if collapsed
1404  * @private
1405  */
1406 YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
1407     var bFireEvent = false;
1408     var width = this._oContainer._oContent.offsetWidth + "px";
1409     var height = this._oContainer._oContent.offsetHeight + "px";
1411     if(this.useIFrame && this._oContainer._oIFrame) {
1412         bFireEvent = true;
1413         if(bShow) {
1414             this._oContainer._oIFrame.style.width = width;
1415             this._oContainer._oIFrame.style.height = height;
1416         }
1417         else {
1418             this._oContainer._oIFrame.style.width = 0;
1419             this._oContainer._oIFrame.style.height = 0;
1420         }
1421     }
1422     if(this.useShadow && this._oContainer._oShadow) {
1423         bFireEvent = true;
1424         if(bShow) {
1425             this._oContainer._oShadow.style.width = width;
1426             this._oContainer._oShadow.style.height = height;
1427         }
1428         else {
1429            this._oContainer._oShadow.style.width = 0;
1430             this._oContainer._oShadow.style.height = 0;
1431         }
1432     }
1436  * Animates expansion or collapse of the container.
1438  * @method _toggleContainer
1439  * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
1440  * @private
1441  */
1442 YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
1443     var oContainer = this._oContainer;
1445     // Implementer has container always open so don't mess with it
1446     if(this.alwaysShowContainer && this._bContainerOpen) {
1447         return;
1448     }
1449     
1450     // Clear contents of container
1451     if(!bShow) {
1452         this._oContainer._oContent.scrollTop = 0;
1453         var aItems = this._aListItems;
1455         if(aItems && (aItems.length > 0)) {
1456             for(var i = aItems.length-1; i >= 0 ; i--) {
1457                 aItems[i].style.display = "none";
1458             }
1459         }
1461         if(this._oCurItem) {
1462             this._toggleHighlight(this._oCurItem,"from");
1463         }
1465         this._oCurItem = null;
1466         this._nDisplayedItems = 0;
1467         this._sCurQuery = null;
1468     }
1470     // Container is already closed
1471     if(!bShow && !this._bContainerOpen) {
1472         oContainer._oContent.style.display = "none";
1473         return;
1474     }
1476     // If animation is enabled...
1477     var oAnim = this._oAnim;
1478     if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
1479         // If helpers need to be collapsed, do it right away...
1480         // but if helpers need to be expanded, wait until after the container expands
1481         if(!bShow) {
1482             this._toggleContainerHelpers(bShow);
1483         }
1485         if(oAnim.isAnimated()) {
1486             oAnim.stop();
1487         }
1489         // Clone container to grab current size offscreen
1490         var oClone = oContainer._oContent.cloneNode(true);
1491         oContainer.appendChild(oClone);
1492         oClone.style.top = "-9000px";
1493         oClone.style.display = "block";
1495         // Current size of the container is the EXPANDED size
1496         var wExp = oClone.offsetWidth;
1497         var hExp = oClone.offsetHeight;
1499         // Calculate COLLAPSED sizes based on horiz and vert anim
1500         var wColl = (this.animHoriz) ? 0 : wExp;
1501         var hColl = (this.animVert) ? 0 : hExp;
1503         // Set animation sizes
1504         oAnim.attributes = (bShow) ?
1505             {width: { to: wExp }, height: { to: hExp }} :
1506             {width: { to: wColl}, height: { to: hColl }};
1508         // If opening anew, set to a collapsed size...
1509         if(bShow && !this._bContainerOpen) {
1510             oContainer._oContent.style.width = wColl+"px";
1511             oContainer._oContent.style.height = hColl+"px";
1512         }
1513         // Else, set it to its last known size.
1514         else {
1515             oContainer._oContent.style.width = wExp+"px";
1516             oContainer._oContent.style.height = hExp+"px";
1517         }
1519         oContainer.removeChild(oClone);
1520         oClone = null;
1522         var oSelf = this;
1523         var onAnimComplete = function() {
1524             // Finish the collapse
1525                 oAnim.onComplete.unsubscribeAll();
1527             if(bShow) {
1528                 oSelf.containerExpandEvent.fire(oSelf);
1529             }
1530             else {
1531                 oContainer._oContent.style.display = "none";
1532                 oSelf.containerCollapseEvent.fire(oSelf);
1533             }
1534             oSelf._toggleContainerHelpers(bShow);
1535         };
1537         // Display container and animate it
1538         oContainer._oContent.style.display = "block";
1539         oAnim.onComplete.subscribe(onAnimComplete);
1540         oAnim.animate();
1541         this._bContainerOpen = bShow;
1542     }
1543     // Else don't animate, just show or hide
1544     else {
1545         if(bShow) {
1546             oContainer._oContent.style.display = "block";
1547             this.containerExpandEvent.fire(this);
1548         }
1549         else {
1550             oContainer._oContent.style.display = "none";
1551             this.containerCollapseEvent.fire(this);
1552         }
1553         this._toggleContainerHelpers(bShow);
1554         this._bContainerOpen = bShow;
1555    }
1560  * Toggles the highlight on or off for an item in the container, and also cleans
1561  * up highlighting of any previous item.
1563  * @method _toggleHighlight
1564  * @param oNewItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
1565  * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
1566  * @private
1567  */
1568 YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(oNewItem, sType) {
1569     var sHighlight = this.highlightClassName;
1570     if(this._oCurItem) {
1571         // Remove highlight from old item
1572         YAHOO.util.Dom.removeClass(this._oCurItem, sHighlight);
1573     }
1575     if((sType == "to") && sHighlight) {
1576         // Apply highlight to new item
1577         YAHOO.util.Dom.addClass(oNewItem, sHighlight);
1578         this._oCurItem = oNewItem;
1579     }
1583  * Toggles the pre-highlight on or off for an item in the container.
1585  * @method _togglePrehighlight
1586  * @param oNewItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
1587  * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
1588  * @private
1589  */
1590 YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(oNewItem, sType) {
1591     if(oNewItem == this._oCurItem) {
1592         return;
1593     }
1595     var sPrehighlight = this.prehighlightClassName;
1596     if((sType == "mouseover") && sPrehighlight) {
1597         // Apply prehighlight to new item
1598         YAHOO.util.Dom.addClass(oNewItem, sPrehighlight);
1599     }
1600     else {
1601         // Remove prehighlight from old item
1602         YAHOO.util.Dom.removeClass(oNewItem, sPrehighlight);
1603     }
1607  * Updates the text input box value with selected query result. If a delimiter
1608  * has been defined, then the value gets appended with the delimiter.
1610  * @method _updateValue
1611  * @param oItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
1612  * @private
1613  */
1614 YAHOO.widget.AutoComplete.prototype._updateValue = function(oItem) {
1615     var oTextbox = this._oTextbox;
1616     var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
1617     var sSavedQuery = this._sSavedQuery;
1618     var sResultKey = oItem._sResultKey;
1619     oTextbox.focus();
1621     // First clear text field
1622     oTextbox.value = "";
1623     // Grab data to put into text field
1624     if(sDelimChar) {
1625         if(sSavedQuery) {
1626             oTextbox.value = sSavedQuery;
1627         }
1628         oTextbox.value += sResultKey + sDelimChar;
1629         if(sDelimChar != " ") {
1630             oTextbox.value += " ";
1631         }
1632     }
1633     else { oTextbox.value = sResultKey; }
1635     // scroll to bottom of textarea if necessary
1636     if(oTextbox.type == "textarea") {
1637         oTextbox.scrollTop = oTextbox.scrollHeight;
1638     }
1640     // move cursor to end
1641     var end = oTextbox.value.length;
1642     this._selectText(oTextbox,end,end);
1644     this._oCurItem = oItem;
1648  * Selects a result item from the container
1650  * @method _selectItem
1651  * @param oItem {HTMLElement} The selected &lt;li&gt; element item.
1652  * @private
1653  */
1654 YAHOO.widget.AutoComplete.prototype._selectItem = function(oItem) {
1655     this._bItemSelected = true;
1656     this._updateValue(oItem);
1657     this._cancelIntervalDetection(this);
1658     this.itemSelectEvent.fire(this, oItem, oItem._oResultData);
1659     this._toggleContainer(false);
1663  * If an item is highlighted in the container, the right arrow key jumps to the
1664  * end of the textbox and selects the highlighted item, otherwise the container
1665  * is closed.
1667  * @method _jumpSelection
1668  * @private
1669  */
1670 YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
1671     if(this._oCurItem) {
1672         this._selectItem(this._oCurItem);
1673     }
1674     else {
1675         this._toggleContainer(false);
1676     }
1680  * Triggered by up and down arrow keys, changes the current highlighted
1681  * &lt;li&gt; element item. Scrolls container if necessary.
1683  * @method _moveSelection
1684  * @param nKeyCode {Number} Code of key pressed.
1685  * @private
1686  */
1687 YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
1688     if(this._bContainerOpen) {
1689         // Determine current item's id number
1690         var oCurItem = this._oCurItem;
1691         var nCurItemIndex = -1;
1693         if(oCurItem) {
1694             nCurItemIndex = oCurItem._nItemIndex;
1695         }
1697         var nNewItemIndex = (nKeyCode == 40) ?
1698                 (nCurItemIndex + 1) : (nCurItemIndex - 1);
1700         // Out of bounds
1701         if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
1702             return;
1703         }
1705         if(oCurItem) {
1706             // Unhighlight current item
1707             this._toggleHighlight(oCurItem, "from");
1708             this.itemArrowFromEvent.fire(this, oCurItem);
1709         }
1710         if(nNewItemIndex == -1) {
1711            // Go back to query (remove type-ahead string)
1712             if(this.delimChar && this._sSavedQuery) {
1713                 if(!this._textMatchesOption()) {
1714                     this._oTextbox.value = this._sSavedQuery;
1715                 }
1716                 else {
1717                     this._oTextbox.value = this._sSavedQuery + this._sCurQuery;
1718                 }
1719             }
1720             else {
1721                 this._oTextbox.value = this._sCurQuery;
1722             }
1723             this._oCurItem = null;
1724             return;
1725         }
1726         if(nNewItemIndex == -2) {
1727             // Close container
1728             this._toggleContainer(false);
1729             return;
1730         }
1732         var oNewItem = this._aListItems[nNewItemIndex];
1734         // Scroll the container if necessary
1735         var oContent = this._oContainer._oContent;
1736         var scrollOn = ((YAHOO.util.Dom.getStyle(oContent,"overflow") == "auto") ||
1737             (YAHOO.util.Dom.getStyle(oContent,"overflowY") == "auto"));
1738         if(scrollOn && (nNewItemIndex > -1) &&
1739         (nNewItemIndex < this._nDisplayedItems)) {
1740             // User is keying down
1741             if(nKeyCode == 40) {
1742                 // Bottom of selected item is below scroll area...
1743                 if((oNewItem.offsetTop+oNewItem.offsetHeight) > (oContent.scrollTop + oContent.offsetHeight)) {
1744                     // Set bottom of scroll area to bottom of selected item
1745                     oContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - oContent.offsetHeight;
1746                 }
1747                 // Bottom of selected item is above scroll area...
1748                 else if((oNewItem.offsetTop+oNewItem.offsetHeight) < oContent.scrollTop) {
1749                     // Set top of selected item to top of scroll area
1750                     oContent.scrollTop = oNewItem.offsetTop;
1752                 }
1753             }
1754             // User is keying up
1755             else {
1756                 // Top of selected item is above scroll area
1757                 if(oNewItem.offsetTop < oContent.scrollTop) {
1758                     // Set top of scroll area to top of selected item
1759                     this._oContainer._oContent.scrollTop = oNewItem.offsetTop;
1760                 }
1761                 // Top of selected item is below scroll area
1762                 else if(oNewItem.offsetTop > (oContent.scrollTop + oContent.offsetHeight)) {
1763                     // Set bottom of selected item to bottom of scroll area
1764                     this._oContainer._oContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - oContent.offsetHeight;
1765                 }
1766             }
1767         }
1769         this._toggleHighlight(oNewItem, "to");
1770         this.itemArrowToEvent.fire(this, oNewItem);
1771         if(this.typeAhead) {
1772             this._updateValue(oNewItem);
1773         }
1774     }
1777 /////////////////////////////////////////////////////////////////////////////
1779 // Private event handlers
1781 /////////////////////////////////////////////////////////////////////////////
1784  * Handles &lt;li&gt; element mouseover events in the container.
1786  * @method _onItemMouseover
1787  * @param v {HTMLEvent} The mouseover event.
1788  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1789  * @private
1790  */
1791 YAHOO.widget.AutoComplete.prototype._onItemMouseover = function(v,oSelf) {
1792     if(oSelf.prehighlightClassName) {
1793         oSelf._togglePrehighlight(this,"mouseover");
1794     }
1795     else {
1796         oSelf._toggleHighlight(this,"to");
1797     }
1799     oSelf.itemMouseOverEvent.fire(oSelf, this);
1803  * Handles &lt;li&gt; element mouseout events in the container.
1805  * @method _onItemMouseout
1806  * @param v {HTMLEvent} The mouseout event.
1807  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1808  * @private
1809  */
1810 YAHOO.widget.AutoComplete.prototype._onItemMouseout = function(v,oSelf) {
1811     if(oSelf.prehighlightClassName) {
1812         oSelf._togglePrehighlight(this,"mouseout");
1813     }
1814     else {
1815         oSelf._toggleHighlight(this,"from");
1816     }
1818     oSelf.itemMouseOutEvent.fire(oSelf, this);
1822  * Handles &lt;li&gt; element click events in the container.
1824  * @method _onItemMouseclick
1825  * @param v {HTMLEvent} The click event.
1826  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1827  * @private
1828  */
1829 YAHOO.widget.AutoComplete.prototype._onItemMouseclick = function(v,oSelf) {
1830     // In case item has not been moused over
1831     oSelf._toggleHighlight(this,"to");
1832     oSelf._selectItem(this);
1836  * Handles container mouseover events.
1838  * @method _onContainerMouseover
1839  * @param v {HTMLEvent} The mouseover event.
1840  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1841  * @private
1842  */
1843 YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
1844     oSelf._bOverContainer = true;
1848  * Handles container mouseout events.
1850  * @method _onContainerMouseout
1851  * @param v {HTMLEvent} The mouseout event.
1852  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1853  * @private
1854  */
1855 YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
1856     oSelf._bOverContainer = false;
1857     // If container is still active
1858     if(oSelf._oCurItem) {
1859         oSelf._toggleHighlight(oSelf._oCurItem,"to");
1860     }
1864  * Handles container scroll events.
1866  * @method _onContainerScroll
1867  * @param v {HTMLEvent} The scroll event.
1868  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1869  * @private
1870  */
1871 YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
1872     oSelf._oTextbox.focus();
1876  * Handles container resize events.
1878  * @method _onContainerResize
1879  * @param v {HTMLEvent} The resize event.
1880  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1881  * @private
1882  */
1883 YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
1884     oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
1889  * Handles textbox keydown events of functional keys, mainly for UI behavior.
1891  * @method _onTextboxKeyDown
1892  * @param v {HTMLEvent} The keydown event.
1893  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1894  * @private
1895  */
1896 YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
1897     var nKeyCode = v.keyCode;
1899     switch (nKeyCode) {
1900         case 9: // tab
1901             // select an item or clear out
1902             if(oSelf._oCurItem) {
1903                 if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
1904                     if(oSelf._bContainerOpen) {
1905                         YAHOO.util.Event.stopEvent(v);
1906                     }
1907                 }
1908                 oSelf._selectItem(oSelf._oCurItem);
1909             }
1910             else {
1911                 oSelf._toggleContainer(false);
1912             }
1913             break;
1914         case 13: // enter
1915             if(oSelf._oCurItem) {
1916                 if(oSelf._nKeyCode != nKeyCode) {
1917                     if(oSelf._bContainerOpen) {
1918                         YAHOO.util.Event.stopEvent(v);
1919                     }
1920                 }
1921                 oSelf._selectItem(oSelf._oCurItem);
1922             }
1923             else {
1924                 oSelf._toggleContainer(false);
1925             }
1926             break;
1927         case 27: // esc
1928             oSelf._toggleContainer(false);
1929             return;
1930         case 39: // right
1931             oSelf._jumpSelection();
1932             break;
1933         case 38: // up
1934             YAHOO.util.Event.stopEvent(v);
1935             oSelf._moveSelection(nKeyCode);
1936             break;
1937         case 40: // down
1938             YAHOO.util.Event.stopEvent(v);
1939             oSelf._moveSelection(nKeyCode);
1940             break;
1941         default:
1942             break;
1943     }
1947  * Handles textbox keypress events.
1948  * @method _onTextboxKeyPress
1949  * @param v {HTMLEvent} The keypress event.
1950  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1951  * @private
1952  */
1953 YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
1954     var nKeyCode = v.keyCode;
1956         //Expose only to Mac browsers, where stopEvent is ineffective on keydown events (bug 790337)
1957         var isMac = (navigator.userAgent.toLowerCase().indexOf("mac") != -1);
1958         if(isMac) {
1959             switch (nKeyCode) {
1960             case 9: // tab
1961                 if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
1962                     YAHOO.util.Event.stopEvent(v);
1963                 }
1964                 break;
1965             case 13: // enter
1966                 if(oSelf._nKeyCode != nKeyCode) {
1967                     YAHOO.util.Event.stopEvent(v);
1968                 }
1969                 break;
1970             case 38: // up
1971             case 40: // down
1972                 YAHOO.util.Event.stopEvent(v);
1973                 break;
1974             default:
1975                 break;
1976             }
1977         }
1979         //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
1980         // Korean IME detected
1981         else if(nKeyCode == 229) {
1982             oSelf._queryInterval = setInterval(function() { oSelf._onIMEDetected(oSelf); },500);
1983         }
1987  * Handles textbox keyup events that trigger queries.
1989  * @method _onTextboxKeyUp
1990  * @param v {HTMLEvent} The keyup event.
1991  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1992  * @private
1993  */
1994 YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
1995     // Check to see if any of the public properties have been updated
1996     oSelf._initProps();
1998     var nKeyCode = v.keyCode;
1999     oSelf._nKeyCode = nKeyCode;
2000     var sText = this.value; //string in textbox
2002     // Filter out chars that don't trigger queries
2003     if(oSelf._isIgnoreKey(nKeyCode) || (sText.toLowerCase() == oSelf._sCurQuery)) {
2004         return;
2005     }
2006     else {
2007         oSelf._bItemSelected = false;
2008         YAHOO.util.Dom.removeClass(oSelf._oCurItem,  oSelf.highlightClassName);
2009         oSelf._oCurItem = null;
2011         oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2012     }
2014     // Set timeout on the request
2015     if(oSelf.queryDelay > 0) {
2016         var nDelayID =
2017             setTimeout(function(){oSelf._sendQuery(sText);},(oSelf.queryDelay * 1000));
2019         if(oSelf._nDelayID != -1) {
2020             clearTimeout(oSelf._nDelayID);
2021         }
2023         oSelf._nDelayID = nDelayID;
2024     }
2025     else {
2026         // No delay so send request immediately
2027         oSelf._sendQuery(sText);
2028     }
2032  * Handles text input box receiving focus.
2034  * @method _onTextboxFocus
2035  * @param v {HTMLEvent} The focus event.
2036  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2037  * @private
2038  */
2039 YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2040     oSelf._oTextbox.setAttribute("autocomplete","off");
2041     oSelf._bFocused = true;
2042     if(!oSelf._bItemSelected) {
2043         oSelf.textboxFocusEvent.fire(oSelf);
2044     }
2048  * Handles text input box losing focus.
2050  * @method _onTextboxBlur
2051  * @param v {HTMLEvent} The focus event.
2052  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2053  * @private
2054  */
2055 YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2056     // Don't treat as a blur if it was a selection via mouse click
2057     if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2058         // Current query needs to be validated
2059         if(!oSelf._bItemSelected) {
2060             var oMatch = oSelf._textMatchesOption();
2061             if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (oMatch === null))) {
2062                 if(oSelf.forceSelection) {
2063                     oSelf._clearSelection();
2064                 }
2065                 else {
2066                     oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2067                 }
2068             }
2069             else {
2070                 oSelf._selectItem(oMatch);
2071             }
2072         }
2074         if(oSelf._bContainerOpen) {
2075             oSelf._toggleContainer(false);
2076         }
2077         oSelf._cancelIntervalDetection(oSelf);
2078         oSelf._bFocused = false;
2079         oSelf.textboxBlurEvent.fire(oSelf);
2080     }
2084  * Handles form submission event.
2086  * @method _onFormSubmit
2087  * @param v {HTMLEvent} The submit event.
2088  * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2089  * @private
2090  */
2091 YAHOO.widget.AutoComplete.prototype._onFormSubmit = function(v,oSelf) {
2092     if(oSelf.allowBrowserAutocomplete) {
2093         oSelf._oTextbox.setAttribute("autocomplete","on");
2094     }
2095     else {
2096         oSelf._oTextbox.setAttribute("autocomplete","off");
2097     }
2100 /****************************************************************************/
2101 /****************************************************************************/
2102 /****************************************************************************/
2105  * The DataSource classes manages sending a request and returning response from a live
2106  * database. Supported data include local JavaScript arrays and objects and databases
2107  * accessible via XHR connections. Supported response formats include JavaScript arrays,
2108  * JSON, XML, and flat-file textual data.
2109  *  
2110  * @class DataSource
2111  * @constructor
2112  */
2113 YAHOO.widget.DataSource = function() { 
2114     /* abstract class */
2118 /////////////////////////////////////////////////////////////////////////////
2120 // Public constants
2122 /////////////////////////////////////////////////////////////////////////////
2125  * Error message for null data responses.
2127  * @property ERROR_DATANULL
2128  * @type String
2129  * @static
2130  * @final
2131  */
2132 YAHOO.widget.DataSource.ERROR_DATANULL = "Response data was null";
2135  * Error message for data responses with parsing errors.
2137  * @property ERROR_DATAPARSE
2138  * @type String
2139  * @static
2140  * @final
2141  */
2142 YAHOO.widget.DataSource.ERROR_DATAPARSE = "Response data could not be parsed";
2145 /////////////////////////////////////////////////////////////////////////////
2147 // Public member variables
2149 /////////////////////////////////////////////////////////////////////////////
2152  * Max size of the local cache.  Set to 0 to turn off caching.  Caching is
2153  * useful to reduce the number of server connections.  Recommended only for data
2154  * sources that return comprehensive results for queries or when stale data is
2155  * not an issue.
2157  * @property maxCacheEntries
2158  * @type Number
2159  * @default 15
2160  */
2161 YAHOO.widget.DataSource.prototype.maxCacheEntries = 15;
2164  * Use this to fine-tune the matching algorithm used against JS Array types of
2165  * DataSource and DataSource caches. If queryMatchContains is true, then the JS
2166  * Array or cache returns results that "contain" the query string. By default,
2167  * queryMatchContains is set to false, so that only results that "start with"
2168  * the query string are returned.
2170  * @property queryMatchContains
2171  * @type Boolean
2172  * @default false
2173  */
2174 YAHOO.widget.DataSource.prototype.queryMatchContains = false;
2177  * Enables query subset matching. If caching is on and queryMatchSubset is
2178  * true, substrings of queries will return matching cached results. For
2179  * instance, if the first query is for "abc" susequent queries that start with
2180  * "abc", like "abcd", will be queried against the cache, and not the live data
2181  * source. Recommended only for DataSources that return comprehensive results
2182  * for queries with very few characters.
2184  * @property queryMatchSubset
2185  * @type Boolean
2186  * @default false
2188  */
2189 YAHOO.widget.DataSource.prototype.queryMatchSubset = false;
2192  * Enables case-sensitivity in the matching algorithm used against JS Array
2193  * types of DataSources and DataSource caches. If queryMatchCase is true, only
2194  * case-sensitive matches will return.
2196  * @property queryMatchCase
2197  * @type Boolean
2198  * @default false
2199  */
2200 YAHOO.widget.DataSource.prototype.queryMatchCase = false;
2203 /////////////////////////////////////////////////////////////////////////////
2205 // Public methods
2207 /////////////////////////////////////////////////////////////////////////////
2209  /**
2210  * Public accessor to the unique name of the DataSource instance.
2212  * @method toString
2213  * @return {String} Unique name of the DataSource instance
2214  */
2215 YAHOO.widget.DataSource.prototype.toString = function() {
2216     return "DataSource " + this._sName;
2220  * Retrieves query results, first checking the local cache, then making the
2221  * query request to the live data source as defined by the function doQuery.
2223  * @method getResults
2224  * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
2225  * @param sQuery {String} Query string.
2226  * @param oParent {Object} The object instance that has requested data.
2227  */
2228 YAHOO.widget.DataSource.prototype.getResults = function(oCallbackFn, sQuery, oParent) {
2229     
2230     // First look in cache
2231     var aResults = this._doQueryCache(oCallbackFn,sQuery,oParent);
2232     // Not in cache, so get results from server
2233     if(aResults.length === 0) {
2234         this.queryEvent.fire(this, oParent, sQuery);
2235         this.doQuery(oCallbackFn, sQuery, oParent);
2236     }
2240  * Abstract method implemented by subclasses to make a query to the live data
2241  * source. Must call the callback function with the response returned from the
2242  * query. Populates cache (if enabled).
2244  * @method doQuery
2245  * @param oCallbackFn {HTMLFunction} Callback function implemented by oParent to which to return results.
2246  * @param sQuery {String} Query string.
2247  * @param oParent {Object} The object instance that has requested data.
2248  */
2249 YAHOO.widget.DataSource.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
2250     /* override this */ 
2254  * Flushes cache.
2256  * @method flushCache
2257  */
2258 YAHOO.widget.DataSource.prototype.flushCache = function() {
2259     if(this._aCache) {
2260         this._aCache = [];
2261     }
2262     if(this._aCacheHelper) {
2263         this._aCacheHelper = [];
2264     }
2265     this.cacheFlushEvent.fire(this);
2269 /////////////////////////////////////////////////////////////////////////////
2271 // Public events
2273 /////////////////////////////////////////////////////////////////////////////
2276  * Fired when a query is made to the live data source.
2278  * @event queryEvent
2279  * @param oSelf {Object} The DataSource instance.
2280  * @param oParent {Object} The requesting object.
2281  * @param sQuery {String} The query string.
2282  */
2283 YAHOO.widget.DataSource.prototype.queryEvent = null;
2286  * Fired when a query is made to the local cache.
2288  * @event cacheQueryEvent
2289  * @param oSelf {Object} The DataSource instance.
2290  * @param oParent {Object} The requesting object.
2291  * @param sQuery {String} The query string.
2292  */
2293 YAHOO.widget.DataSource.prototype.cacheQueryEvent = null;
2296  * Fired when data is retrieved from the live data source.
2298  * @event getResultsEvent
2299  * @param oSelf {Object} The DataSource instance.
2300  * @param oParent {Object} The requesting object.
2301  * @param sQuery {String} The query string.
2302  * @param aResults {Object[]} Array of result objects.
2303  */
2304 YAHOO.widget.DataSource.prototype.getResultsEvent = null;
2305     
2307  * Fired when data is retrieved from the local cache.
2309  * @event getCachedResultsEvent
2310  * @param oSelf {Object} The DataSource instance.
2311  * @param oParent {Object} The requesting object.
2312  * @param sQuery {String} The query string.
2313  * @param aResults {Object[]} Array of result objects.
2314  */
2315 YAHOO.widget.DataSource.prototype.getCachedResultsEvent = null;
2318  * Fired when an error is encountered with the live data source.
2320  * @event dataErrorEvent
2321  * @param oSelf {Object} The DataSource instance.
2322  * @param oParent {Object} The requesting object.
2323  * @param sQuery {String} The query string.
2324  * @param sMsg {String} Error message string
2325  */
2326 YAHOO.widget.DataSource.prototype.dataErrorEvent = null;
2329  * Fired when the local cache is flushed.
2331  * @event cacheFlushEvent
2332  * @param oSelf {Object} The DataSource instance
2333  */
2334 YAHOO.widget.DataSource.prototype.cacheFlushEvent = null;
2336 /////////////////////////////////////////////////////////////////////////////
2338 // Private member variables
2340 /////////////////////////////////////////////////////////////////////////////
2343  * Internal class variable to index multiple DataSource instances.
2345  * @property _nIndex
2346  * @type Number
2347  * @private
2348  * @static
2349  */
2350 YAHOO.widget.DataSource._nIndex = 0;
2353  * Name of DataSource instance.
2355  * @property _sName
2356  * @type String
2357  * @private
2358  */
2359 YAHOO.widget.DataSource.prototype._sName = null;
2362  * Local cache of data result objects indexed chronologically.
2364  * @property _aCache
2365  * @type Object[]
2366  * @private
2367  */
2368 YAHOO.widget.DataSource.prototype._aCache = null;
2371 /////////////////////////////////////////////////////////////////////////////
2373 // Private methods
2375 /////////////////////////////////////////////////////////////////////////////
2378  * Initializes DataSource instance.
2379  *  
2380  * @method _init
2381  * @private
2382  */
2383 YAHOO.widget.DataSource.prototype._init = function() {
2384     // Validate and initialize public configs
2385     var maxCacheEntries = this.maxCacheEntries;
2386     if(!YAHOO.lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
2387         maxCacheEntries = 0;
2388     }
2389     // Initialize local cache
2390     if(maxCacheEntries > 0 && !this._aCache) {
2391         this._aCache = [];
2392     }
2393     
2394     this._sName = "instance" + YAHOO.widget.DataSource._nIndex;
2395     YAHOO.widget.DataSource._nIndex++;
2396     
2397     this.queryEvent = new YAHOO.util.CustomEvent("query", this);
2398     this.cacheQueryEvent = new YAHOO.util.CustomEvent("cacheQuery", this);
2399     this.getResultsEvent = new YAHOO.util.CustomEvent("getResults", this);
2400     this.getCachedResultsEvent = new YAHOO.util.CustomEvent("getCachedResults", this);
2401     this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
2402     this.cacheFlushEvent = new YAHOO.util.CustomEvent("cacheFlush", this);
2406  * Adds a result object to the local cache, evicting the oldest element if the 
2407  * cache is full. Newer items will have higher indexes, the oldest item will have
2408  * index of 0. 
2410  * @method _addCacheElem
2411  * @param oResult {Object} Data result object, including array of results.
2412  * @private
2413  */
2414 YAHOO.widget.DataSource.prototype._addCacheElem = function(oResult) {
2415     var aCache = this._aCache;
2416     // Don't add if anything important is missing.
2417     if(!aCache || !oResult || !oResult.query || !oResult.results) {
2418         return;
2419     }
2420     
2421     // If the cache is full, make room by removing from index=0
2422     if(aCache.length >= this.maxCacheEntries) {
2423         aCache.shift();
2424     }
2425         
2426     // Add to cache, at the end of the array
2427     aCache.push(oResult);
2431  * Queries the local cache for results. If query has been cached, the callback
2432  * function is called with the results, and the cached is refreshed so that it
2433  * is now the newest element.  
2435  * @method _doQueryCache
2436  * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
2437  * @param sQuery {String} Query string.
2438  * @param oParent {Object} The object instance that has requested data.
2439  * @return aResults {Object[]} Array of results from local cache if found, otherwise null.
2440  * @private 
2441  */
2442 YAHOO.widget.DataSource.prototype._doQueryCache = function(oCallbackFn, sQuery, oParent) {
2443     var aResults = [];
2444     var bMatchFound = false;
2445     var aCache = this._aCache;
2446     var nCacheLength = (aCache) ? aCache.length : 0;
2447     var bMatchContains = this.queryMatchContains;
2448     
2449     // If cache is enabled...
2450     if((this.maxCacheEntries > 0) && aCache && (nCacheLength > 0)) {
2451         this.cacheQueryEvent.fire(this, oParent, sQuery);
2452         // If case is unimportant, normalize query now instead of in loops
2453         if(!this.queryMatchCase) {
2454             var sOrigQuery = sQuery;
2455             sQuery = sQuery.toLowerCase();
2456         }
2458         // Loop through each cached element's query property...
2459         for(var i = nCacheLength-1; i >= 0; i--) {
2460             var resultObj = aCache[i];
2461             var aAllResultItems = resultObj.results;
2462             // If case is unimportant, normalize match key for comparison
2463             var matchKey = (!this.queryMatchCase) ?
2464                 encodeURIComponent(resultObj.query).toLowerCase():
2465                 encodeURIComponent(resultObj.query);
2466             
2467             // If a cached match key exactly matches the query...
2468             if(matchKey == sQuery) {
2469                     // Stash all result objects into aResult[] and stop looping through the cache.
2470                     bMatchFound = true;
2471                     aResults = aAllResultItems;
2472                     
2473                     // The matching cache element was not the most recent,
2474                     // so now we need to refresh the cache.
2475                     if(i != nCacheLength-1) {                        
2476                         // Remove element from its original location
2477                         aCache.splice(i,1);
2478                         // Add element as newest
2479                         this._addCacheElem(resultObj);
2480                     }
2481                     break;
2482             }
2483             // Else if this query is not an exact match and subset matching is enabled...
2484             else if(this.queryMatchSubset) {
2485                 // Loop through substrings of each cached element's query property...
2486                 for(var j = sQuery.length-1; j >= 0 ; j--) {
2487                     var subQuery = sQuery.substr(0,j);
2488                     
2489                     // If a substring of a cached sQuery exactly matches the query...
2490                     if(matchKey == subQuery) {                    
2491                         bMatchFound = true;
2492                         
2493                         // Go through each cached result object to match against the query...
2494                         for(var k = aAllResultItems.length-1; k >= 0; k--) {
2495                             var aRecord = aAllResultItems[k];
2496                             var sKeyIndex = (this.queryMatchCase) ?
2497                                 encodeURIComponent(aRecord[0]).indexOf(sQuery):
2498                                 encodeURIComponent(aRecord[0]).toLowerCase().indexOf(sQuery);
2499                             
2500                             // A STARTSWITH match is when the query is found at the beginning of the key string...
2501                             if((!bMatchContains && (sKeyIndex === 0)) ||
2502                             // A CONTAINS match is when the query is found anywhere within the key string...
2503                             (bMatchContains && (sKeyIndex > -1))) {
2504                                 // Stash a match into aResults[].
2505                                 aResults.unshift(aRecord);
2506                             }
2507                         }
2508                         
2509                         // Add the subset match result set object as the newest element to cache,
2510                         // and stop looping through the cache.
2511                         resultObj = {};
2512                         resultObj.query = sQuery;
2513                         resultObj.results = aResults;
2514                         this._addCacheElem(resultObj);
2515                         break;
2516                     }
2517                 }
2518                 if(bMatchFound) {
2519                     break;
2520                 }
2521             }
2522         }
2523         
2524         // If there was a match, send along the results.
2525         if(bMatchFound) {
2526             this.getCachedResultsEvent.fire(this, oParent, sOrigQuery, aResults);
2527             oCallbackFn(sOrigQuery, aResults, oParent);
2528         }
2529     }
2530     return aResults;
2534 /****************************************************************************/
2535 /****************************************************************************/
2536 /****************************************************************************/
2539  * Implementation of YAHOO.widget.DataSource using XML HTTP requests that return
2540  * query results.
2541  *  
2542  * @class DS_XHR
2543  * @extends YAHOO.widget.DataSource
2544  * @requires connection
2545  * @constructor
2546  * @param sScriptURI {String} Absolute or relative URI to script that returns query
2547  * results as JSON, XML, or delimited flat-file data.
2548  * @param aSchema {String[]} Data schema definition of results.
2549  * @param oConfigs {Object} (optional) Object literal of config params.
2550  */
2551 YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
2552     // Set any config params passed in to override defaults
2553     if(oConfigs && (oConfigs.constructor == Object)) {
2554         for(var sConfig in oConfigs) {
2555             this[sConfig] = oConfigs[sConfig];
2556         }
2557     }
2559     // Initialization sequence
2560     if(!YAHOO.lang.isArray(aSchema) || !YAHOO.lang.isString(sScriptURI)) {
2561         return;
2562     }
2564     this.schema = aSchema;
2565     this.scriptURI = sScriptURI;
2566     
2567     this._init();
2570 YAHOO.widget.DS_XHR.prototype = new YAHOO.widget.DataSource();
2572 /////////////////////////////////////////////////////////////////////////////
2574 // Public constants
2576 /////////////////////////////////////////////////////////////////////////////
2579  * JSON data type.
2581  * @property TYPE_JSON
2582  * @type Number
2583  * @static
2584  * @final
2585  */
2586 YAHOO.widget.DS_XHR.TYPE_JSON = 0;
2589  * XML data type.
2591  * @property TYPE_XML
2592  * @type Number
2593  * @static
2594  * @final
2595  */
2596 YAHOO.widget.DS_XHR.TYPE_XML = 1;
2599  * Flat-file data type.
2601  * @property TYPE_FLAT
2602  * @type Number
2603  * @static
2604  * @final
2605  */
2606 YAHOO.widget.DS_XHR.TYPE_FLAT = 2;
2609  * Error message for XHR failure.
2611  * @property ERROR_DATAXHR
2612  * @type String
2613  * @static
2614  * @final
2615  */
2616 YAHOO.widget.DS_XHR.ERROR_DATAXHR = "XHR response failed";
2618 /////////////////////////////////////////////////////////////////////////////
2620 // Public member variables
2622 /////////////////////////////////////////////////////////////////////////////
2625  * Alias to YUI Connection Manager. Allows implementers to specify their own
2626  * subclasses of the YUI Connection Manager utility.
2628  * @property connMgr
2629  * @type Object
2630  * @default YAHOO.util.Connect
2631  */
2632 YAHOO.widget.DS_XHR.prototype.connMgr = YAHOO.util.Connect;
2635  * Number of milliseconds the XHR connection will wait for a server response. A
2636  * a value of zero indicates the XHR connection will wait forever. Any value
2637  * greater than zero will use the Connection utility's Auto-Abort feature.
2639  * @property connTimeout
2640  * @type Number
2641  * @default 0
2642  */
2643 YAHOO.widget.DS_XHR.prototype.connTimeout = 0;
2646  * Absolute or relative URI to script that returns query results. For instance,
2647  * queries will be sent to &#60;scriptURI&#62;?&#60;scriptQueryParam&#62;=userinput
2649  * @property scriptURI
2650  * @type String
2651  */
2652 YAHOO.widget.DS_XHR.prototype.scriptURI = null;
2655  * Query string parameter name sent to scriptURI. For instance, queries will be
2656  * sent to &#60;scriptURI&#62;?&#60;scriptQueryParam&#62;=userinput
2658  * @property scriptQueryParam
2659  * @type String
2660  * @default "query"
2661  */
2662 YAHOO.widget.DS_XHR.prototype.scriptQueryParam = "query";
2665  * String of key/value pairs to append to requests made to scriptURI. Define
2666  * this string when you want to send additional query parameters to your script.
2667  * When defined, queries will be sent to
2668  * &#60;scriptURI&#62;?&#60;scriptQueryParam&#62;=userinput&#38;&#60;scriptQueryAppend&#62;
2670  * @property scriptQueryAppend
2671  * @type String
2672  * @default ""
2673  */
2674 YAHOO.widget.DS_XHR.prototype.scriptQueryAppend = "";
2677  * XHR response data type. Other types that may be defined are YAHOO.widget.DS_XHR.TYPE_XML
2678  * and YAHOO.widget.DS_XHR.TYPE_FLAT.
2680  * @property responseType
2681  * @type String
2682  * @default YAHOO.widget.DS_XHR.TYPE_JSON
2683  */
2684 YAHOO.widget.DS_XHR.prototype.responseType = YAHOO.widget.DS_XHR.TYPE_JSON;
2687  * String after which to strip results. If the results from the XHR are sent
2688  * back as HTML, the gzip HTML comment appears at the end of the data and should
2689  * be ignored.
2691  * @property responseStripAfter
2692  * @type String
2693  * @default "\n&#60;!-"
2694  */
2695 YAHOO.widget.DS_XHR.prototype.responseStripAfter = "\n<!-";
2697 /////////////////////////////////////////////////////////////////////////////
2699 // Public methods
2701 /////////////////////////////////////////////////////////////////////////////
2704  * Queries the live data source defined by scriptURI for results. Results are
2705  * passed back to a callback function.
2706  *  
2707  * @method doQuery
2708  * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
2709  * @param sQuery {String} Query string.
2710  * @param oParent {Object} The object instance that has requested data.
2711  */
2712 YAHOO.widget.DS_XHR.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
2713     var isXML = (this.responseType == YAHOO.widget.DS_XHR.TYPE_XML);
2714     var sUri = this.scriptURI+"?"+this.scriptQueryParam+"="+sQuery;
2715     if(this.scriptQueryAppend.length > 0) {
2716         sUri += "&" + this.scriptQueryAppend;
2717     }
2718     var oResponse = null;
2719     
2720     var oSelf = this;
2721     /*
2722      * Sets up ajax request callback
2723      *
2724      * @param {object} oReq          HTTPXMLRequest object
2725      * @private
2726      */
2727     var responseSuccess = function(oResp) {
2728         // Response ID does not match last made request ID.
2729         if(!oSelf._oConn || (oResp.tId != oSelf._oConn.tId)) {
2730             oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
2731             return;
2732         }
2733 //DEBUG
2734 for(var foo in oResp) {
2736         if(!isXML) {
2737             oResp = oResp.responseText;
2738         }
2739         else { 
2740             oResp = oResp.responseXML;
2741         }
2742         if(oResp === null) {
2743             oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
2744             return;
2745         }
2747         var aResults = oSelf.parseResponse(sQuery, oResp, oParent);
2748         var resultObj = {};
2749         resultObj.query = decodeURIComponent(sQuery);
2750         resultObj.results = aResults;
2751         if(aResults === null) {
2752             oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE);
2753             aResults = [];
2754         }
2755         else {
2756             oSelf.getResultsEvent.fire(oSelf, oParent, sQuery, aResults);
2757             oSelf._addCacheElem(resultObj);
2758         }
2759         oCallbackFn(sQuery, aResults, oParent);
2760     };
2762     var responseFailure = function(oResp) {
2763         oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DS_XHR.ERROR_DATAXHR);
2764         return;
2765     };
2766     
2767     var oCallback = {
2768         success:responseSuccess,
2769         failure:responseFailure
2770     };
2771     
2772     if(YAHOO.lang.isNumber(this.connTimeout) && (this.connTimeout > 0)) {
2773         oCallback.timeout = this.connTimeout;
2774     }
2775     
2776     if(this._oConn) {
2777         this.connMgr.abort(this._oConn);
2778     }
2779     
2780     oSelf._oConn = this.connMgr.asyncRequest("GET", sUri, oCallback, null);
2784  * Parses raw response data into an array of result objects. The result data key
2785  * is always stashed in the [0] element of each result object. 
2787  * @method parseResponse
2788  * @param sQuery {String} Query string.
2789  * @param oResponse {Object} The raw response data to parse.
2790  * @param oParent {Object} The object instance that has requested data.
2791  * @returns {Object[]} Array of result objects.
2792  */
2793 YAHOO.widget.DS_XHR.prototype.parseResponse = function(sQuery, oResponse, oParent) {
2794     var aSchema = this.schema;
2795     var aResults = [];
2796     var bError = false;
2798     // Strip out comment at the end of results
2799     var nEnd = ((this.responseStripAfter !== "") && (oResponse.indexOf)) ?
2800         oResponse.indexOf(this.responseStripAfter) : -1;
2801     if(nEnd != -1) {
2802         oResponse = oResponse.substring(0,nEnd);
2803     }
2805     switch (this.responseType) {
2806         case YAHOO.widget.DS_XHR.TYPE_JSON:
2807             var jsonList, jsonObjParsed;
2808             // Check for JSON lib but divert KHTML clients
2809             var isNotMac = (navigator.userAgent.toLowerCase().indexOf('khtml')== -1);
2810             if(oResponse.parseJSON && isNotMac) {
2811                 // Use the new JSON utility if available
2812                 jsonObjParsed = oResponse.parseJSON();
2813                 if(!jsonObjParsed) {
2814                     bError = true;
2815                 }
2816                 else {
2817                     try {
2818                         // eval is necessary here since aSchema[0] is of unknown depth
2819                         jsonList = eval("jsonObjParsed." + aSchema[0]);
2820                     }
2821                     catch(e) {
2822                         bError = true;
2823                         break;
2824                    }
2825                 }
2826             }
2827             else if(window.JSON && isNotMac) {
2828                 // Use older JSON lib if available
2829                 jsonObjParsed = JSON.parse(oResponse);
2830                 if(!jsonObjParsed) {
2831                     bError = true;
2832                     break;
2833                 }
2834                 else {
2835                     try {
2836                         // eval is necessary here since aSchema[0] is of unknown depth
2837                         jsonList = eval("jsonObjParsed." + aSchema[0]);
2838                     }
2839                     catch(e) {
2840                         bError = true;
2841                         break;
2842                    }
2843                 }
2844             }
2845             else {
2846                 // Parse the JSON response as a string
2847                 try {
2848                     // Trim leading spaces
2849                     while (oResponse.substring(0,1) == " ") {
2850                         oResponse = oResponse.substring(1, oResponse.length);
2851                     }
2853                     // Invalid JSON response
2854                     if(oResponse.indexOf("{") < 0) {
2855                         bError = true;
2856                         break;
2857                     }
2859                     // Empty (but not invalid) JSON response
2860                     if(oResponse.indexOf("{}") === 0) {
2861                         break;
2862                     }
2864                     // Turn the string into an object literal...
2865                     // ...eval is necessary here
2866                     var jsonObjRaw = eval("(" + oResponse + ")");
2867                     if(!jsonObjRaw) {
2868                         bError = true;
2869                         break;
2870                     }
2872                     // Grab the object member that contains an array of all reponses...
2873                     // ...eval is necessary here since aSchema[0] is of unknown depth
2874                     jsonList = eval("(jsonObjRaw." + aSchema[0]+")");
2875                 }
2876                 catch(e) {
2877                     bError = true;
2878                     break;
2879                }
2880             }
2882             if(!jsonList) {
2883                 bError = true;
2884                 break;
2885             }
2887             if(!YAHOO.lang.isArray(jsonList)) {
2888                 jsonList = [jsonList];
2889             }
2890             
2891             // Loop through the array of all responses...
2892             for(var i = jsonList.length-1; i >= 0 ; i--) {
2893                 var aResultItem = [];
2894                 var jsonResult = jsonList[i];
2895                 // ...and loop through each data field value of each response
2896                 for(var j = aSchema.length-1; j >= 1 ; j--) {
2897                     // ...and capture data into an array mapped according to the schema...
2898                     var dataFieldValue = jsonResult[aSchema[j]];
2899                     if(!dataFieldValue) {
2900                         dataFieldValue = "";
2901                     }
2902                     aResultItem.unshift(dataFieldValue);
2903                 }
2904                 // If schema isn't well defined, pass along the entire result object
2905                 if(aResultItem.length == 1) {
2906                     aResultItem.push(jsonResult);
2907                 }
2908                 // Capture the array of data field values in an array of results
2909                 aResults.unshift(aResultItem);
2910             }
2911             break;
2912         case YAHOO.widget.DS_XHR.TYPE_XML:
2913             // Get the collection of results
2914             var xmlList = oResponse.getElementsByTagName(aSchema[0]);
2915             if(!xmlList) {
2916                 bError = true;
2917                 break;
2918             }
2919             // Loop through each result
2920             for(var k = xmlList.length-1; k >= 0 ; k--) {
2921                 var result = xmlList.item(k);
2922                 var aFieldSet = [];
2923                 // Loop through each data field in each result using the schema
2924                 for(var m = aSchema.length-1; m >= 1 ; m--) {
2925                     var sValue = null;
2926                     // Values may be held in an attribute...
2927                     var xmlAttr = result.attributes.getNamedItem(aSchema[m]);
2928                     if(xmlAttr) {
2929                         sValue = xmlAttr.value;
2930                     }
2931                     // ...or in a node
2932                     else{
2933                         var xmlNode = result.getElementsByTagName(aSchema[m]);
2934                         if(xmlNode && xmlNode.item(0) && xmlNode.item(0).firstChild) {
2935                             sValue = xmlNode.item(0).firstChild.nodeValue;
2936                         }
2937                         else {
2938                             sValue = "";
2939                         }
2940                     }
2941                     // Capture the schema-mapped data field values into an array
2942                     aFieldSet.unshift(sValue);
2943                 }
2944                 // Capture each array of values into an array of results
2945                 aResults.unshift(aFieldSet);
2946             }
2947             break;
2948         case YAHOO.widget.DS_XHR.TYPE_FLAT:
2949             if(oResponse.length > 0) {
2950                 // Delete the last line delimiter at the end of the data if it exists
2951                 var newLength = oResponse.length-aSchema[0].length;
2952                 if(oResponse.substr(newLength) == aSchema[0]) {
2953                     oResponse = oResponse.substr(0, newLength);
2954                 }
2955                 var aRecords = oResponse.split(aSchema[0]);
2956                 for(var n = aRecords.length-1; n >= 0; n--) {
2957                     aResults[n] = aRecords[n].split(aSchema[1]);
2958                 }
2959             }
2960             break;
2961         default:
2962             break;
2963     }
2964     sQuery = null;
2965     oResponse = null;
2966     oParent = null;
2967     if(bError) {
2968         return null;
2969     }
2970     else {
2971         return aResults;
2972     }
2973 };            
2975 /////////////////////////////////////////////////////////////////////////////
2977 // Private member variables
2979 /////////////////////////////////////////////////////////////////////////////
2982  * XHR connection object.
2984  * @property _oConn
2985  * @type Object
2986  * @private
2987  */
2988 YAHOO.widget.DS_XHR.prototype._oConn = null;
2991 /****************************************************************************/
2992 /****************************************************************************/
2993 /****************************************************************************/
2996  * Implementation of YAHOO.widget.DataSource using a native Javascript function as
2997  * its live data source.
2998  *  
2999  * @class DS_JSFunction
3000  * @constructor
3001  * @extends YAHOO.widget.DataSource
3002  * @param oFunction {HTMLFunction} In-memory Javascript function that returns query results as an array of objects.
3003  * @param oConfigs {Object} (optional) Object literal of config params.
3004  */
3005 YAHOO.widget.DS_JSFunction = function(oFunction, oConfigs) {
3006     // Set any config params passed in to override defaults
3007     if(oConfigs && (oConfigs.constructor == Object)) {
3008         for(var sConfig in oConfigs) {
3009             this[sConfig] = oConfigs[sConfig];
3010         }
3011     }
3013     // Initialization sequence
3014     if(!YAHOO.lang.isFunction(oFunction)) {
3015         return;
3016     }
3017     else {
3018         this.dataFunction = oFunction;
3019         this._init();
3020     }
3023 YAHOO.widget.DS_JSFunction.prototype = new YAHOO.widget.DataSource();
3025 /////////////////////////////////////////////////////////////////////////////
3027 // Public member variables
3029 /////////////////////////////////////////////////////////////////////////////
3032  * In-memory Javascript function that returns query results.
3034  * @property dataFunction
3035  * @type HTMLFunction
3036  */
3037 YAHOO.widget.DS_JSFunction.prototype.dataFunction = null;
3039 /////////////////////////////////////////////////////////////////////////////
3041 // Public methods
3043 /////////////////////////////////////////////////////////////////////////////
3046  * Queries the live data source defined by function for results. Results are
3047  * passed back to a callback function.
3048  *  
3049  * @method doQuery
3050  * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
3051  * @param sQuery {String} Query string.
3052  * @param oParent {Object} The object instance that has requested data.
3053  */
3054 YAHOO.widget.DS_JSFunction.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
3055     var oFunction = this.dataFunction;
3056     var aResults = [];
3057     
3058     aResults = oFunction(sQuery);
3059     if(aResults === null) {
3060         this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
3061         return;
3062     }
3063     
3064     var resultObj = {};
3065     resultObj.query = decodeURIComponent(sQuery);
3066     resultObj.results = aResults;
3067     this._addCacheElem(resultObj);
3068     
3069     this.getResultsEvent.fire(this, oParent, sQuery, aResults);
3070     oCallbackFn(sQuery, aResults, oParent);
3071     return;
3074 /****************************************************************************/
3075 /****************************************************************************/
3076 /****************************************************************************/
3079  * Implementation of YAHOO.widget.DataSource using a native Javascript array as
3080  * its live data source.
3082  * @class DS_JSArray
3083  * @constructor
3084  * @extends YAHOO.widget.DataSource
3085  * @param aData {String[]} In-memory Javascript array of simple string data.
3086  * @param oConfigs {Object} (optional) Object literal of config params.
3087  */
3088 YAHOO.widget.DS_JSArray = function(aData, oConfigs) {
3089     // Set any config params passed in to override defaults
3090     if(oConfigs && (oConfigs.constructor == Object)) {
3091         for(var sConfig in oConfigs) {
3092             this[sConfig] = oConfigs[sConfig];
3093         }
3094     }
3096     // Initialization sequence
3097     if(!YAHOO.lang.isArray(aData)) {
3098         return;
3099     }
3100     else {
3101         this.data = aData;
3102         this._init();
3103     }
3106 YAHOO.widget.DS_JSArray.prototype = new YAHOO.widget.DataSource();
3108 /////////////////////////////////////////////////////////////////////////////
3110 // Public member variables
3112 /////////////////////////////////////////////////////////////////////////////
3115  * In-memory Javascript array of strings.
3117  * @property data
3118  * @type Array
3119  */
3120 YAHOO.widget.DS_JSArray.prototype.data = null;
3122 /////////////////////////////////////////////////////////////////////////////
3124 // Public methods
3126 /////////////////////////////////////////////////////////////////////////////
3129  * Queries the live data source defined by data for results. Results are passed
3130  * back to a callback function.
3132  * @method doQuery
3133  * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
3134  * @param sQuery {String} Query string.
3135  * @param oParent {Object} The object instance that has requested data.
3136  */
3137 YAHOO.widget.DS_JSArray.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
3138     var i;
3139     var aData = this.data; // the array
3140     var aResults = []; // container for results
3141     var bMatchFound = false;
3142     var bMatchContains = this.queryMatchContains;
3143     if(sQuery) {
3144         if(!this.queryMatchCase) {
3145             sQuery = sQuery.toLowerCase();
3146         }
3148         // Loop through each element of the array...
3149         // which can be a string or an array of strings
3150         for(i = aData.length-1; i >= 0; i--) {
3151             var aDataset = [];
3153             if(YAHOO.lang.isString(aData[i])) {
3154                 aDataset[0] = aData[i];
3155             }
3156             else if(YAHOO.lang.isArray(aData[i])) {
3157                 aDataset = aData[i];
3158             }
3160             if(YAHOO.lang.isString(aDataset[0])) {
3161                 var sKeyIndex = (this.queryMatchCase) ?
3162                 encodeURIComponent(aDataset[0]).indexOf(sQuery):
3163                 encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery);
3165                 // A STARTSWITH match is when the query is found at the beginning of the key string...
3166                 if((!bMatchContains && (sKeyIndex === 0)) ||
3167                 // A CONTAINS match is when the query is found anywhere within the key string...
3168                 (bMatchContains && (sKeyIndex > -1))) {
3169                     // Stash a match into aResults[].
3170                     aResults.unshift(aDataset);
3171                 }
3172             }
3173         }
3174     }
3175     else {
3176         for(i = aData.length-1; i >= 0; i--) {
3177             if(YAHOO.lang.isString(aData[i])) {
3178                 aResults.unshift([aData[i]]);
3179             }
3180             else if(YAHOO.lang.isArray(aData[i])) {
3181                 aResults.unshift(aData[i]);
3182             }
3183         }
3184     }
3185     
3186     this.getResultsEvent.fire(this, oParent, sQuery, aResults);
3187     oCallbackFn(sQuery, aResults, oParent);
3190 YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.3.0", build: "442"});